wolfhece 2.2.1__py3-none-any.whl → 2.2.3__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.
- wolfhece/PandasGrid.py +67 -0
- wolfhece/PyDraw.py +671 -30
- wolfhece/PyGui.py +6 -2
- wolfhece/PyHydrographs.py +2 -3
- wolfhece/PyPalette.py +10 -0
- wolfhece/PyParams.py +2 -3
- wolfhece/PyVertex.py +7 -7
- wolfhece/PyVertexvectors.py +1 -0
- wolfhece/PyWMS.py +72 -3
- wolfhece/acceptability/Parallels.py +25 -19
- wolfhece/acceptability/acceptability_gui.py +104 -131
- wolfhece/acceptability/func.py +99 -13
- wolfhece/apps/check_install.py +6 -6
- wolfhece/apps/splashscreen.py +5 -0
- wolfhece/apps/version.py +1 -1
- wolfhece/assets/__init__.py +1 -0
- wolfhece/assets/speedometer.py +135 -0
- wolfhece/dike.py +684 -0
- wolfhece/drowning_victims/Class.py +2165 -0
- wolfhece/drowning_victims/Functions.py +1019 -0
- wolfhece/drowning_victims/__init__.py +0 -0
- wolfhece/lifewatch.py +88 -0
- wolfhece/scenario/config_manager.py +4 -4
- wolfhece/wolf_array.py +58 -33
- wolfhece/wolf_texture.py +9 -2
- {wolfhece-2.2.1.dist-info → wolfhece-2.2.3.dist-info}/METADATA +3 -1
- {wolfhece-2.2.1.dist-info → wolfhece-2.2.3.dist-info}/RECORD +30 -22
- {wolfhece-2.2.1.dist-info → wolfhece-2.2.3.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.1.dist-info → wolfhece-2.2.3.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.1.dist-info → wolfhece-2.2.3.dist-info}/top_level.txt +0 -0
wolfhece/dike.py
ADDED
@@ -0,0 +1,684 @@
|
|
1
|
+
import wx
|
2
|
+
import wx.propgrid as pg
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
from os.path import join
|
6
|
+
from pathlib import Path
|
7
|
+
import logging
|
8
|
+
import subprocess
|
9
|
+
import numpy as np
|
10
|
+
from matplotlib.widgets import Slider, Button
|
11
|
+
from matplotlib import pyplot as plt
|
12
|
+
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
|
13
|
+
import matplotlib.tri as tri
|
14
|
+
import time
|
15
|
+
|
16
|
+
from wolfpydike.dikeBreaching import pyDike
|
17
|
+
|
18
|
+
from .drawing_obj import Element_To_Draw
|
19
|
+
from .PyParams import Wolf_Param, Type_Param, Buttons, key_Param
|
20
|
+
from .PyDraw import Triangulation
|
21
|
+
from .matplotlib_fig import Matplotlib_Figure
|
22
|
+
from .PyTranslate import _
|
23
|
+
|
24
|
+
class DikeWolf(Element_To_Draw):
|
25
|
+
|
26
|
+
def __init__(self, idx = '', plotted = True, mapviewer=None, need_for_wx = False):
|
27
|
+
super().__init__(idx, plotted, mapviewer, need_for_wx)
|
28
|
+
|
29
|
+
self.filename = None
|
30
|
+
self.wp = None
|
31
|
+
self._dike = pyDike()
|
32
|
+
|
33
|
+
def run(self, store_dir=None):
|
34
|
+
"""
|
35
|
+
Run the dike breaching simulation.
|
36
|
+
:param store_dir: Directory where the simulation will be run.
|
37
|
+
"""
|
38
|
+
try:
|
39
|
+
self._dike.run(store_dir=store_dir)
|
40
|
+
logging.info(_("Breaching simulation done."))
|
41
|
+
except subprocess.CalledProcessError as e:
|
42
|
+
logging.error(_("Error while running the breaching simulation: %s") % e)
|
43
|
+
|
44
|
+
def callback_apply(self):
|
45
|
+
"""
|
46
|
+
Callback function to apply changes made in the Wolf_Param window.
|
47
|
+
Update the parameters in the dike object.
|
48
|
+
"""
|
49
|
+
updated_wp = self.wp.merge_dicts()
|
50
|
+
self.from_wp_to_dict(wolf_dict=updated_wp, dict_ref=self.get_params())
|
51
|
+
|
52
|
+
def show_properties(self):
|
53
|
+
"""
|
54
|
+
Show properties window
|
55
|
+
"""
|
56
|
+
if self.wp is None:
|
57
|
+
self.wp = self.from_dict_to_wp(params_dict=self.get_params())
|
58
|
+
self.wp.set_callbacks(callback_update=self.callback_apply, callback_destroy=None)
|
59
|
+
self.wp._set_gui_dike(title=_('Parameters for simulation with default parameters already set'))
|
60
|
+
self.wp.hide_selected_buttons([Buttons.Reload,Buttons.Save])
|
61
|
+
self.wp.Show()
|
62
|
+
|
63
|
+
def hide_properties(self):
|
64
|
+
"""
|
65
|
+
Hide properties window
|
66
|
+
"""
|
67
|
+
if self.wp is not None:
|
68
|
+
self.wp.Hide()
|
69
|
+
|
70
|
+
def from_wp_to_dict(self, wolf_dict, dict_ref) -> dict:
|
71
|
+
"""
|
72
|
+
Convert a Wolf_Param dictionary to a "normal" dictionary used as parameters dictionary in the pydike object + updates pydike attributes accordingly.
|
73
|
+
'dict_ref' used to rename keys (=mapping).
|
74
|
+
:param wolf_dict: Dictionary containing the parameters from the Wolf_Param object.
|
75
|
+
:param dict_ref: Dictionary mapping pydike parameter names (keys) to explicit names in wolf_param.
|
76
|
+
:return: Dictionary with pydike parameter names as keys, containing values and metadata.
|
77
|
+
"""
|
78
|
+
params_dict = {}
|
79
|
+
|
80
|
+
for section in wolf_dict.keys():
|
81
|
+
params_dict[section] = {}
|
82
|
+
for param_data in wolf_dict[section].values():
|
83
|
+
explicit_name = param_data[key_Param.NAME] # Extract explicit name
|
84
|
+
|
85
|
+
# Search for the corresponding pydike_key inside dict_ref
|
86
|
+
pydike_key = None
|
87
|
+
for section_name, section_params in dict_ref.items():
|
88
|
+
for param_key, param_details in section_params.items():
|
89
|
+
if param_details.get("explicit name") == explicit_name:
|
90
|
+
pydike_key = param_key # Get the correct parameter key
|
91
|
+
break
|
92
|
+
if pydike_key: # Exit the outer loop if found
|
93
|
+
break
|
94
|
+
|
95
|
+
if pydike_key is None:
|
96
|
+
print(_("Warning: No match found in dict_ref for '%s'") % explicit_name)
|
97
|
+
continue # Skip if no match is found
|
98
|
+
|
99
|
+
params_dict[section][pydike_key] = {
|
100
|
+
"value": param_data[key_Param.VALUE],
|
101
|
+
"description": param_data[key_Param.COMMENT],
|
102
|
+
"explicit name": explicit_name,
|
103
|
+
"type": param_data[key_Param.TYPE],
|
104
|
+
"choices": dict_ref[section_name][pydike_key].get("choices"),
|
105
|
+
"mandatory": dict_ref[section_name][pydike_key].get("mandatory"),
|
106
|
+
}
|
107
|
+
|
108
|
+
self._dike.update_paramsDict(params_dict)
|
109
|
+
|
110
|
+
return
|
111
|
+
|
112
|
+
def from_dict_to_wp(self,params_dict) -> Wolf_Param:
|
113
|
+
""" Modify the Wolf_Param object to represent the dike parameters. """
|
114
|
+
|
115
|
+
wp = Wolf_Param_dike(parent = None, # Contains all the parameters of the window
|
116
|
+
title = _("Breaching of a dike"),
|
117
|
+
to_read=False,
|
118
|
+
withbuttons=True,
|
119
|
+
toShow=False,
|
120
|
+
init_GUI=False,
|
121
|
+
force_even_if_same_default = True,
|
122
|
+
filename="default_name.json",
|
123
|
+
DestroyAtClosing=False)
|
124
|
+
|
125
|
+
for current_section in params_dict.keys():
|
126
|
+
for key in params_dict[current_section].keys():
|
127
|
+
|
128
|
+
value = params_dict[current_section][key]["value"]
|
129
|
+
description = params_dict[current_section][key]["description"]
|
130
|
+
name = params_dict[current_section][key]["explicit name"]
|
131
|
+
# Parameter type
|
132
|
+
if params_dict[current_section][key]["type"] == "Float":
|
133
|
+
type_param = Type_Param.Float
|
134
|
+
elif params_dict[current_section][key]["type"] == "Integer":
|
135
|
+
type_param = Type_Param.Integer
|
136
|
+
elif params_dict[current_section][key]["type"] == "Logical":
|
137
|
+
type_param = Type_Param.Logical
|
138
|
+
elif params_dict[current_section][key]["type"] == "String":
|
139
|
+
type_param = Type_Param.String
|
140
|
+
elif params_dict[current_section][key]["type"] == "Directory":
|
141
|
+
type_param = Type_Param.Directory
|
142
|
+
elif params_dict[current_section][key]["type"] == "File":
|
143
|
+
type_param = Type_Param.File
|
144
|
+
|
145
|
+
if params_dict[current_section][key]["choices"] != None:
|
146
|
+
wp.add_param((current_section), (name), value, type_param, whichdict='Default', jsonstr={"Values":params_dict[current_section][key]["choices"]}, comment=_(description))
|
147
|
+
if params_dict[current_section][key]["mandatory"]:
|
148
|
+
wp.add_param((current_section), (name), value, type_param, whichdict='Active', jsonstr={"Values":params_dict[current_section][key]["choices"]}, comment=_(description))
|
149
|
+
else:
|
150
|
+
wp.add_param((current_section), (name), value, type_param, whichdict='Default', comment=_(description))
|
151
|
+
if params_dict[current_section][key]["mandatory"]:
|
152
|
+
wp.add_param((current_section), (name), value, type_param, whichdict='Active', comment=_(description))
|
153
|
+
return wp
|
154
|
+
|
155
|
+
def load_results(self):
|
156
|
+
"""
|
157
|
+
Load the main outputs and/or the triangulation of the simulation.
|
158
|
+
"""
|
159
|
+
filterArray = "Parameter files (*_params.json)|*_params.json"
|
160
|
+
dlg = wx.FileDialog(self.mapviewer, _('Choose the file containing the simulation parametrization'), wildcard=filterArray, style=wx.FD_FILE_MUST_EXIST)
|
161
|
+
ret=dlg.ShowModal()
|
162
|
+
if ret == wx.ID_CANCEL:
|
163
|
+
dlg.Destroy()
|
164
|
+
self.param_path = dlg.GetPath()
|
165
|
+
if self.param_path.endswith("_params.json"):
|
166
|
+
gen_path = self.param_path.removesuffix("_params.json")
|
167
|
+
else:
|
168
|
+
logging.warning(_("ERROR : the name of the file containing the simulation parametrization should end with '_params.json'"))
|
169
|
+
dlg.Destroy()
|
170
|
+
return
|
171
|
+
|
172
|
+
self.param_path = Path(self.param_path)
|
173
|
+
self.filename = (self.param_path.name).removesuffix("_params.json")
|
174
|
+
self.read_params(file_name=self.filename+"_params", store_dir=self.param_path.parent)
|
175
|
+
|
176
|
+
try:
|
177
|
+
mainOutputs_path = Path(gen_path + '_mainOutputs.txt')
|
178
|
+
mainOutputs_dict = {}
|
179
|
+
with open(mainOutputs_path, "r", encoding="utf-8") as f:
|
180
|
+
lines = f.readlines()
|
181
|
+
|
182
|
+
if not lines:
|
183
|
+
logging.warning(_("ERROR: The file '%s' is empty.") % mainOutputs_path)
|
184
|
+
else:
|
185
|
+
# Extract column headers
|
186
|
+
keys = lines[0].strip().split("\t")
|
187
|
+
|
188
|
+
# Initialize dictionary with empty lists
|
189
|
+
mainOutputs_dict = {key: [] for key in keys}
|
190
|
+
|
191
|
+
# Populate lists with float values
|
192
|
+
for line in lines[1:]:
|
193
|
+
values = line.strip().split("\t")
|
194
|
+
for key, value in zip(keys, values):
|
195
|
+
try:
|
196
|
+
mainOutputs_dict[key].append(float(value)) # cast to float
|
197
|
+
except ValueError:
|
198
|
+
logging.warning(_("Non-float value encountered: %s (in key: %s)") % (value, key))
|
199
|
+
self.set_series(mainOutputs_dict)
|
200
|
+
except FileNotFoundError:
|
201
|
+
logging.warning(_("WARNING: The file containing the main outputs does not exist. The following format is expected: 'TESTNAME_mainOutputs.txt'"))
|
202
|
+
|
203
|
+
try:
|
204
|
+
triangulation_path = Path(gen_path + '_triangulation.json')
|
205
|
+
with open(triangulation_path, 'r') as f:
|
206
|
+
triangulation_dict = json.load(f)
|
207
|
+
self.set_triangulation(triangulation_dict)
|
208
|
+
except FileNotFoundError:
|
209
|
+
logging.warning(_("WARNING : the file containing the triangulation does not exist. The following format is expected: 'TESTNAME_triangulation.json'"))
|
210
|
+
|
211
|
+
def set_series(self, mainOutputs_dict):
|
212
|
+
"""
|
213
|
+
Set the main outputs in the dike object.
|
214
|
+
"""
|
215
|
+
self._dike.set_series_fromDict(mainOutputs_dict)
|
216
|
+
|
217
|
+
def set_triangulation(self, triangulation_dict):
|
218
|
+
"""
|
219
|
+
Set the triangulation in the dike object.
|
220
|
+
"""
|
221
|
+
self._dike.triangulation_dict = triangulation_dict
|
222
|
+
|
223
|
+
@property
|
224
|
+
def mainOutputs_dict(self):
|
225
|
+
"""
|
226
|
+
Get the main outputs of the simulation as a dictionary. Time [s] / Qin [m^3/s] / Btop_US [m] / Btop_DS [m] / z_b [m] / Qb [m^3/s] / z_s [m] / z_t [m]
|
227
|
+
:return: dictionary containing the main outputs
|
228
|
+
"""
|
229
|
+
return self._dike.get_series_toDict()
|
230
|
+
|
231
|
+
@property
|
232
|
+
def triangulation_dict(self):
|
233
|
+
"""
|
234
|
+
Get the triangulation of the simulation.
|
235
|
+
:return: dictionary containing the triangulation
|
236
|
+
"""
|
237
|
+
return self._dike.triangulation_dict
|
238
|
+
|
239
|
+
def show_triangulation(self):
|
240
|
+
"""
|
241
|
+
Plot a graph that shows the dike triangulation.
|
242
|
+
"""
|
243
|
+
|
244
|
+
def update_triangulation(time_idx: int):
|
245
|
+
|
246
|
+
# Get the XYZ and triangles for the given time
|
247
|
+
XYZ = np.array(self.triangulation_dict[str(time_idx)]["XYZ"])
|
248
|
+
triangles = np.array(self.triangulation_dict[str(time_idx)]["idx_triangles"])
|
249
|
+
time = times_tri[time_idx]
|
250
|
+
|
251
|
+
# Update the vertices and face colors of the Poly3DCollection
|
252
|
+
poly_collection.set_verts(XYZ[triangles])
|
253
|
+
|
254
|
+
# Update the title with the current time value
|
255
|
+
template = _("Elapsed time: {hours:.0f} [h] {minutes:.0f} [min] {seconds:.0f} [s]")
|
256
|
+
elapsed = template.format(hours=np.floor(time / 3600), minutes=np.floor(time / 60) % 60, seconds=time % 60)
|
257
|
+
ax.set_title(elapsed, fontsize=18)
|
258
|
+
|
259
|
+
# Refresh the plot
|
260
|
+
fig.canvas.draw_idle()
|
261
|
+
|
262
|
+
def update_triangulation_safe(val):
|
263
|
+
"""
|
264
|
+
Safely update the triangulation plot, throttling updates to avoid excessive redraws.
|
265
|
+
"""
|
266
|
+
current_time = time.time()
|
267
|
+
if current_time - last_update[0] > 0.2: # Only update every 0.2 seconds
|
268
|
+
update_triangulation(int(val))
|
269
|
+
last_update[0] = current_time
|
270
|
+
|
271
|
+
# Prepare data for the initial plot
|
272
|
+
times_tri = [entry["time"] for entry in self.triangulation_dict.values()]
|
273
|
+
XYZ_ini = np.array(self.triangulation_dict["0"]["XYZ"])
|
274
|
+
triangles_ini = np.array(self.triangulation_dict["0"]["idx_triangles"])
|
275
|
+
|
276
|
+
# Calculate axis limits
|
277
|
+
min_val_x, max_val_x = np.min(XYZ_ini[:, 0]), np.max(XYZ_ini[:, 0])
|
278
|
+
min_val_y, max_val_y = np.min(XYZ_ini[:, 1]), np.max(XYZ_ini[:, 1])
|
279
|
+
min_val_z, max_val_z = np.min(XYZ_ini[:, 2]), np.max(XYZ_ini[:, 2])
|
280
|
+
|
281
|
+
range_x = max_val_x - min_val_x
|
282
|
+
range_y = max_val_y - min_val_y
|
283
|
+
range_z = max_val_z - min_val_z
|
284
|
+
max_range = max(range_x, range_y, range_z)
|
285
|
+
|
286
|
+
# Create the figure and 3D axes
|
287
|
+
fig = plt.figure(figsize=(8, 8))
|
288
|
+
ax = fig.add_subplot(111, projection='3d')
|
289
|
+
|
290
|
+
# Create the initial Poly3DCollection
|
291
|
+
poly_collection = Poly3DCollection(XYZ_ini[triangles_ini], edgecolor='k', facecolor=(0.9, 0.7, 0.5), linewidth=0.2)
|
292
|
+
ax.add_collection3d(poly_collection)
|
293
|
+
|
294
|
+
# Set axis limits and labels
|
295
|
+
ax.set_xlim([min_val_x, max_val_x])
|
296
|
+
ax.set_ylim([min_val_y, max_val_y])
|
297
|
+
ax.set_zlim([min_val_z, max_val_z])
|
298
|
+
ax.set_xlabel('X [m]')
|
299
|
+
ax.set_ylabel('Y [m]')
|
300
|
+
ax.set_zlabel('Z [m]')
|
301
|
+
ax.set_box_aspect([range_x / max_range, range_y / max_range, range_z / max_range])
|
302
|
+
|
303
|
+
# --- Slider ---
|
304
|
+
ax_slider = plt.axes([0.1, 0.05, 0.65, 0.03], facecolor='lightgoldenrodyellow')
|
305
|
+
time_slider = Slider(ax_slider, _('Time'), 0, len(times_tri) - 1, valinit=0,
|
306
|
+
valstep=int(np.ceil(len(times_tri) / 200)))
|
307
|
+
|
308
|
+
# Connect the slider to the update function
|
309
|
+
time_slider.on_changed(update_triangulation_safe)
|
310
|
+
|
311
|
+
# --- Animation ---
|
312
|
+
anim_running = [False]
|
313
|
+
current_frame = [0]
|
314
|
+
last_update = [0]
|
315
|
+
|
316
|
+
def toggle_animation(event):
|
317
|
+
"""
|
318
|
+
Start or stop the animation.
|
319
|
+
"""
|
320
|
+
anim_running[0] = not anim_running[0]
|
321
|
+
anim_button.label.set_text(_("Stop") if anim_running[0] else _("Start"))
|
322
|
+
if anim_running[0]:
|
323
|
+
current_frame[0] = int(time_slider.val)
|
324
|
+
timer.start()
|
325
|
+
else:
|
326
|
+
timer.stop()
|
327
|
+
|
328
|
+
timer = fig.canvas.new_timer(interval=100) # 100ms between frames
|
329
|
+
|
330
|
+
def run_animation(event=None):
|
331
|
+
"""
|
332
|
+
Run the animation by updating the slider value.
|
333
|
+
"""
|
334
|
+
if anim_running[0]:
|
335
|
+
if current_frame[0] < len(times_tri):
|
336
|
+
time_slider.set_val(current_frame[0])
|
337
|
+
current_frame[0] += 1
|
338
|
+
else:
|
339
|
+
anim_running[0] = False
|
340
|
+
anim_button.label.set_text(_("Start"))
|
341
|
+
timer.stop()
|
342
|
+
|
343
|
+
timer.add_callback(run_animation)
|
344
|
+
|
345
|
+
ax_anim = plt.axes([0.8, 0.05, 0.1, 0.03])
|
346
|
+
anim_button = Button(ax_anim, _('Start'))
|
347
|
+
anim_button.on_clicked(toggle_animation)
|
348
|
+
|
349
|
+
# --- Save Frame Button ---
|
350
|
+
ax_save = plt.axes([0.78, 0.91, 0.15, 0.05]) # [left, bottom, width, height]
|
351
|
+
btn_save = Button(ax_save, _('Save Frame'))
|
352
|
+
|
353
|
+
def save_frame(event):
|
354
|
+
"""
|
355
|
+
Save the current frame as a PNG file.
|
356
|
+
"""
|
357
|
+
current_idx = int(time_slider.val)
|
358
|
+
filename = f"{self.filename}_frame_{current_idx:03d}.png"
|
359
|
+
fig.savefig(self.param_path.parent / filename, dpi=300)
|
360
|
+
logging.info(_("Saved: %s") % filename)
|
361
|
+
|
362
|
+
btn_save.on_clicked(save_frame)
|
363
|
+
|
364
|
+
# --- Add Triangulation to tree Button ---
|
365
|
+
ax_add = plt.axes([0.78, 0.84, 0.15, 0.05]) # [left, bottom, width, height]
|
366
|
+
btn_add = Button(ax_add, _('Add current\nframe to viewer'))
|
367
|
+
|
368
|
+
def add_tri(event):
|
369
|
+
"""
|
370
|
+
Add current frame to viewer.
|
371
|
+
"""
|
372
|
+
current_idx = int(time_slider.val)
|
373
|
+
currenttri_dike = self.extract_triangulation(current_idx)
|
374
|
+
if currenttri_dike is not None:
|
375
|
+
self.mapviewer.add_object(newobj=currenttri_dike, which='triangulation', id=_("Triangulation_{filename}_{index:03d}").format(filename=self.filename, index=current_idx))
|
376
|
+
logging.info(_("Added triangulation for time index %d to viewer.") % current_idx)
|
377
|
+
|
378
|
+
btn_add.on_clicked(add_tri)
|
379
|
+
|
380
|
+
plt.show()
|
381
|
+
|
382
|
+
def extract_triangulation(self, time_idx:int) -> dict:
|
383
|
+
"""
|
384
|
+
Extract the triangulation for a specific time index.
|
385
|
+
:param time_idx: Time index to extract the triangulation.
|
386
|
+
:return: Triangulation object.
|
387
|
+
"""
|
388
|
+
if str(time_idx) in self.triangulation_dict:
|
389
|
+
XYZ = np.array(self.triangulation_dict[str(time_idx)]["XYZ"])
|
390
|
+
triangles = np.array(self.triangulation_dict[str(time_idx)]["idx_triangles"])
|
391
|
+
return Triangulation(pts=XYZ, tri=triangles)
|
392
|
+
else:
|
393
|
+
logging.warning(_("No triangulation data available for the specified time index."))
|
394
|
+
return None
|
395
|
+
|
396
|
+
def plot_mainOutputs(self, output_type:int):
|
397
|
+
|
398
|
+
fig = Matplotlib_Figure()
|
399
|
+
fig.presets()
|
400
|
+
ax = fig.ax[0]
|
401
|
+
ax.set_xlabel(_('Time [s]'))
|
402
|
+
if output_type == 0:
|
403
|
+
fig.SetTitle(_('Discharges'))
|
404
|
+
ax.set_ylabel(_('Discharges [m^3/s]'))
|
405
|
+
fig.plot(x=self.mainOutputs_dict['Time [s]'], y=self.mainOutputs_dict['Qin [m^3/s]'], ax=0, label='Qin')
|
406
|
+
fig.plot(x=self.mainOutputs_dict['Time [s]'], y=self.mainOutputs_dict['Qb [m^3/s]'], ax=0, label='Qb')
|
407
|
+
elif output_type == 1:
|
408
|
+
fig.SetTitle(_('Water levels and breach bottom elevation'))
|
409
|
+
ax.set_ylabel(_('Water level/breach bottom [m]'))
|
410
|
+
fig.plot(x=self.mainOutputs_dict['Time [s]'], y=self.mainOutputs_dict['z_b [m]'], ax=0, label='z_b')
|
411
|
+
fig.plot(x=self.mainOutputs_dict['Time [s]'], y=self.mainOutputs_dict['z_s [m]'], ax=0, label='z_s')
|
412
|
+
fig.plot(x=self.mainOutputs_dict['Time [s]'], y=self.mainOutputs_dict['z_t [m]'], ax=0, label='z_t')
|
413
|
+
elif output_type == 2:
|
414
|
+
fig.SetTitle(_('Breach widening'))
|
415
|
+
ax.set_ylabel(_('Breach widening [m]'))
|
416
|
+
Btop_DS = np.array(self.mainOutputs_dict['Btop_DS [m]'])
|
417
|
+
Btop_US = np.array(self.mainOutputs_dict['Btop_US [m]'])
|
418
|
+
Btop = Btop_DS - Btop_US
|
419
|
+
fig.plot(x=self.mainOutputs_dict['Time [s]'], y=Btop_US, ax=0, label=_('U/S extremity'))
|
420
|
+
fig.plot(x=self.mainOutputs_dict['Time [s]'], y=Btop_DS, ax=0, label=_('D/S extremity'))
|
421
|
+
fig.plot(x=self.mainOutputs_dict['Time [s]'], y=Btop, ax=0, label=_('Breach top width'))
|
422
|
+
|
423
|
+
def save(self):
|
424
|
+
'''
|
425
|
+
Save the parameters in a .json text file
|
426
|
+
'''
|
427
|
+
if self.filename is None:
|
428
|
+
self.save_as()
|
429
|
+
else:
|
430
|
+
with open(self.filename, 'w') as f:
|
431
|
+
json.dump(self.get_params(), f, indent=4)
|
432
|
+
|
433
|
+
def save_as(self):
|
434
|
+
'''
|
435
|
+
Save the parameters in a .json text file
|
436
|
+
'''
|
437
|
+
filterArray = "json (*.json)|*.json|all (*.*)|*.*"
|
438
|
+
fdlg = wx.FileDialog(None, _("Where should the parameters be stored (.json file)?"), wildcard=filterArray, style=wx.FD_SAVE)
|
439
|
+
ret = fdlg.ShowModal()
|
440
|
+
if ret == wx.ID_OK:
|
441
|
+
self.filename = fdlg.GetPath()
|
442
|
+
self.save()
|
443
|
+
|
444
|
+
fdlg.Destroy()
|
445
|
+
|
446
|
+
def get_params(self):
|
447
|
+
'''
|
448
|
+
Get the parameters of the dike model
|
449
|
+
:return: dictionary containing the parameters
|
450
|
+
'''
|
451
|
+
return self._dike.get_params()
|
452
|
+
|
453
|
+
def read_params(self, file_name:str, store_dir: Path = None):
|
454
|
+
'''
|
455
|
+
Read the model parameters and store them in a dictionary + updates attributes accordingly
|
456
|
+
:param file_name: name of the file to read
|
457
|
+
:param store_dir: directory where to read the file
|
458
|
+
'''
|
459
|
+
self._dike.read_params(file_name, store_dir)
|
460
|
+
|
461
|
+
|
462
|
+
class Wolf_Param_dike(Wolf_Param):
|
463
|
+
def __init__(self, parent = None, title = _("Default Title"), w = 500, h = 800, ontop = False, to_read = True, filename = '', withbuttons = True, DestroyAtClosing = True, toShow = True, init_GUI = True, force_even_if_same_default = False, toolbar = True):
|
464
|
+
super().__init__(parent, title, w, h, ontop, to_read, filename, withbuttons, DestroyAtClosing, toShow, init_GUI, force_even_if_same_default, toolbar)
|
465
|
+
|
466
|
+
def _set_gui_dike(self,
|
467
|
+
parent:wx.Window = None,
|
468
|
+
title:str = _("Default Title"),
|
469
|
+
w:int = 500,
|
470
|
+
h:int = 800,
|
471
|
+
ontop:bool = False,
|
472
|
+
to_read:bool = True,
|
473
|
+
withbuttons:bool = True,
|
474
|
+
DestroyAtClosing:bool = False,
|
475
|
+
toShow:bool = True,
|
476
|
+
full_style = False,
|
477
|
+
toolbar:bool = True):
|
478
|
+
"""
|
479
|
+
Set the GUI if wxPython is running. This function is specifically dedicated to the creation of a dike object.
|
480
|
+
|
481
|
+
Gui is based on wxPropertyGridManager.
|
482
|
+
|
483
|
+
On the left, there is a group of buttons to load, save, apply or reload the parameters.
|
484
|
+
On the right, there is the wxPropertyGridManager for the default and active parameters. Active parameters are displayed in bold.
|
485
|
+
|
486
|
+
To activate a parameter, double-click on it in the default tab. It will be copied to the active tab and the value will be modifiable.
|
487
|
+
|
488
|
+
:param parent : parent frame
|
489
|
+
:param title : title of the frame
|
490
|
+
:param w : width of the frame
|
491
|
+
:param h : height of the frame
|
492
|
+
:param ontop : if True, the frame will be on top of all other windows
|
493
|
+
:param to_read : if True, the file will be read
|
494
|
+
:param withbuttons : if True, buttons will be displayed
|
495
|
+
:param DestroyAtClosing : if True, the frame will be destroyed when closed
|
496
|
+
:param toShow : if True, the frame will be displayed
|
497
|
+
:param full_style : if True, the full style of the PropertyGridManager will be displayed even if ontop is True
|
498
|
+
"""
|
499
|
+
|
500
|
+
self.wx_exists = wx.App.Get() is not None # test if wx App is running
|
501
|
+
|
502
|
+
if not self.wx_exists:
|
503
|
+
logging.error(_("wxPython is not running - Impossible to set the GUI"))
|
504
|
+
return
|
505
|
+
|
506
|
+
#Appel à l'initialisation d'un frame général
|
507
|
+
if ontop:
|
508
|
+
wx.Frame.__init__(self, parent, title=title, size=(w,h),style=wx.DEFAULT_FRAME_STYLE| wx.STAY_ON_TOP)
|
509
|
+
else:
|
510
|
+
wx.Frame.__init__(self, parent, title=title, size=(w,h),style=wx.DEFAULT_FRAME_STYLE)
|
511
|
+
|
512
|
+
self.Bind(wx.EVT_CLOSE,self.OnClose)
|
513
|
+
self.DestroyAtClosing = DestroyAtClosing
|
514
|
+
|
515
|
+
#découpage de la fenêtre
|
516
|
+
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
|
517
|
+
|
518
|
+
if withbuttons:
|
519
|
+
self.sizerbut = wx.BoxSizer(wx.VERTICAL)
|
520
|
+
#boutons
|
521
|
+
self.saveme = wx.Button(self,id=10,label=_("Save to file"))
|
522
|
+
self.loadme = wx.Button(self,id=10,label=_("Load from file"))
|
523
|
+
self.applychange = wx.Button(self,id=10,label=_("Apply change"))
|
524
|
+
self.reloadme = wx.Button(self,id=10,label=_("Reload"))
|
525
|
+
|
526
|
+
#liaison des actions des boutons
|
527
|
+
self.saveme.Bind(wx.EVT_BUTTON,self.SavetoFile)
|
528
|
+
self.loadme.Bind(wx.EVT_BUTTON,self.LoadFromFile_json) # To open a .json file
|
529
|
+
self.reloadme.Bind(wx.EVT_BUTTON,self.Reload)
|
530
|
+
self.applychange.Bind(wx.EVT_BUTTON,self.ApplytoMemory)
|
531
|
+
|
532
|
+
#ajout d'un widget de gestion de propriétés
|
533
|
+
if ontop:
|
534
|
+
if full_style:
|
535
|
+
self.prop = pg.PropertyGridManager(self,
|
536
|
+
style = pg.PG_BOLD_MODIFIED|pg.PG_SPLITTER_AUTO_CENTER|
|
537
|
+
# Include toolbar.
|
538
|
+
pg.PG_TOOLBAR if toolbar else 0 |
|
539
|
+
# Include description box.
|
540
|
+
pg.PG_DESCRIPTION |
|
541
|
+
pg.PG_TOOLTIPS |
|
542
|
+
# Plus defaults.
|
543
|
+
pg.PGMAN_DEFAULT_STYLE
|
544
|
+
)
|
545
|
+
else:
|
546
|
+
self.prop = pg.PropertyGridManager(self,
|
547
|
+
style = pg.PG_BOLD_MODIFIED|pg.PG_SPLITTER_AUTO_CENTER|
|
548
|
+
pg.PG_TOOLTIPS |
|
549
|
+
# Plus defaults.
|
550
|
+
pg.PGMAN_DEFAULT_STYLE
|
551
|
+
)
|
552
|
+
else:
|
553
|
+
self.prop = pg.PropertyGridManager(self,
|
554
|
+
style = pg.PG_BOLD_MODIFIED|pg.PG_SPLITTER_AUTO_CENTER|
|
555
|
+
# Include description box.
|
556
|
+
pg.PG_DESCRIPTION |
|
557
|
+
pg.PG_TOOLTIPS |
|
558
|
+
# Plus defaults.
|
559
|
+
pg.PGMAN_DEFAULT_STYLE |
|
560
|
+
# Include toolbar.
|
561
|
+
pg.PG_TOOLBAR if toolbar else 0
|
562
|
+
)
|
563
|
+
|
564
|
+
self.prop.Bind(pg.EVT_PG_DOUBLE_CLICK,self.OnDblClick)
|
565
|
+
|
566
|
+
#ajout au sizer
|
567
|
+
if withbuttons:
|
568
|
+
self.sizerbut.Add(self.loadme,0,wx.EXPAND)
|
569
|
+
self.sizerbut.Add(self.saveme,1,wx.EXPAND)
|
570
|
+
self.sizerbut.Add(self.applychange,1,wx.EXPAND)
|
571
|
+
self.sizerbut.Add(self.reloadme,1,wx.EXPAND)
|
572
|
+
self.sizer.Add(self.sizerbut,0,wx.EXPAND)
|
573
|
+
self.sizer.Add(self.prop,1,wx.EXPAND)
|
574
|
+
|
575
|
+
if to_read:
|
576
|
+
self.Populate()
|
577
|
+
|
578
|
+
#ajout du sizert à la page
|
579
|
+
self.SetSizer(self.sizer)
|
580
|
+
# self.SetSize(w,h)
|
581
|
+
self.SetAutoLayout(1)
|
582
|
+
self.sizer.Fit(self)
|
583
|
+
|
584
|
+
self.SetSize(0,0,w,h)
|
585
|
+
# self.prop.SetDescBoxHeight(80)
|
586
|
+
|
587
|
+
#affichage de la page
|
588
|
+
self.Show(toShow)
|
589
|
+
|
590
|
+
def LoadFromFile_json(self, event:wx.MouseEvent):
|
591
|
+
""" Load parameters from file """
|
592
|
+
|
593
|
+
temp_dict_active = self.myparams.copy() # Save the current parameters in a temporary dictionary
|
594
|
+
temp_dict_default = self.myparams_default.copy() # Save the current parameters in a temporary dictionary
|
595
|
+
|
596
|
+
# read the file
|
597
|
+
if self.wx_exists:
|
598
|
+
#ouverture d'une boîte de dialogue
|
599
|
+
file=wx.FileDialog(self,_("Choose .json file"), wildcard="json (*.json)|*.json|all (*.*)|*.*")
|
600
|
+
if file.ShowModal() == wx.ID_CANCEL:
|
601
|
+
return
|
602
|
+
else:
|
603
|
+
self.Clear() # Clear the parameters before loading new ones
|
604
|
+
#récuparétaion du nom de fichier avec chemin d'accès
|
605
|
+
self.filename =file.GetPath()
|
606
|
+
else:
|
607
|
+
logging.warning(_("ERROR : no filename given and wxPython is not running"))
|
608
|
+
return
|
609
|
+
|
610
|
+
if not os.path.isfile(self.filename):
|
611
|
+
logging.warning(_("ERROR : cannot find the following file : {}".format(self.filename)))
|
612
|
+
return
|
613
|
+
|
614
|
+
with open(self.filename, 'r') as f:
|
615
|
+
myparams_update = json.load(f)
|
616
|
+
|
617
|
+
myparams_update = (self.update_param_window(params_dict=myparams_update, whichdict='Active')).myparams
|
618
|
+
self.myparams = self.merge_dicts(dict_new=myparams_update, dict_ref=temp_dict_active)
|
619
|
+
self.myparams_default = temp_dict_default.copy() # Restore the default parameters
|
620
|
+
|
621
|
+
if self._callback is not None:
|
622
|
+
self._callback()
|
623
|
+
|
624
|
+
# populate the property grid
|
625
|
+
self.Populate()
|
626
|
+
|
627
|
+
def update_param_window(self,params_dict,whichdict) -> Wolf_Param:
|
628
|
+
""" Transforms a params_dict into a Wolf_Param object to fill the 'whichdict' page of the parameters window.
|
629
|
+
:param params_dict: dictionary containing the parameters
|
630
|
+
:param whichdict: dictionary to fill (default or active)"""
|
631
|
+
|
632
|
+
wp = Wolf_Param_dike(parent=None, # Contains all the parameters of the window
|
633
|
+
to_read=False,
|
634
|
+
withbuttons=True,
|
635
|
+
toShow=False,
|
636
|
+
init_GUI=False,
|
637
|
+
force_even_if_same_default = False,
|
638
|
+
filename=join("default_name.json"))
|
639
|
+
|
640
|
+
for current_section in params_dict.keys():
|
641
|
+
for key in params_dict[current_section].keys():
|
642
|
+
|
643
|
+
value = params_dict[current_section][key]["value"]
|
644
|
+
description = params_dict[current_section][key]["description"]
|
645
|
+
name = params_dict[current_section][key]["explicit name"]
|
646
|
+
# Parameter type
|
647
|
+
if params_dict[current_section][key]["type"] == "Float":
|
648
|
+
type_param = Type_Param.Float
|
649
|
+
elif params_dict[current_section][key]["type"] == "Integer":
|
650
|
+
type_param = Type_Param.Integer
|
651
|
+
elif params_dict[current_section][key]["type"] == "Logical":
|
652
|
+
type_param = Type_Param.Logical
|
653
|
+
elif params_dict[current_section][key]["type"] == "String":
|
654
|
+
type_param = Type_Param.String
|
655
|
+
elif params_dict[current_section][key]["type"] == "Directory":
|
656
|
+
type_param = Type_Param.Directory
|
657
|
+
elif params_dict[current_section][key]["type"] == "File":
|
658
|
+
type_param = Type_Param.File
|
659
|
+
|
660
|
+
if params_dict[current_section][key]["choices"] != None:
|
661
|
+
wp.add_param((current_section), (name), value, type_param, whichdict=whichdict, jsonstr={_("Values"):params_dict[current_section][key]["choices"]}, comment=_(description))
|
662
|
+
else:
|
663
|
+
wp.add_param((current_section), (name), value, type_param, whichdict=whichdict, comment=_(description))
|
664
|
+
|
665
|
+
return wp
|
666
|
+
|
667
|
+
def merge_dicts(self, dict_new=None, dict_ref=None):
|
668
|
+
"""
|
669
|
+
Merge values of dict_new into dict_ref.
|
670
|
+
"""
|
671
|
+
if dict_new is None:
|
672
|
+
dict_new = self.myparams
|
673
|
+
if dict_ref is None:
|
674
|
+
dict_ref = self.myparams_default
|
675
|
+
|
676
|
+
for section, params in dict_new.items():
|
677
|
+
if section not in dict_ref:
|
678
|
+
dict_ref[section] = {}
|
679
|
+
for key, value in params.items():
|
680
|
+
if key in dict_ref[section]:
|
681
|
+
dict_ref[section][key].update(value)
|
682
|
+
else:
|
683
|
+
dict_ref[section][key] = value
|
684
|
+
return dict_ref
|