wolfhece 2.2.2__py3-none-any.whl → 2.2.4__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/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
File without changes