wolfhece 2.2.28__py3-none-any.whl → 2.2.29__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.
@@ -11,6 +11,7 @@ from tempfile import NamedTemporaryFile
11
11
  from datetime import datetime as dt
12
12
 
13
13
  import matplotlib.pyplot as plt
14
+ import matplotlib as mpl
14
15
  import seaborn as sns
15
16
 
16
17
  import pymupdf as pdf
@@ -30,6 +31,7 @@ class SimpleSimGPU_Report():
30
31
  def __init__(self, sim:SimpleSimulation | Path | str, **kwargs):
31
32
  """ Initialize the Simple Simulation GPU Report Viewer """
32
33
 
34
+ self._summary = {}
33
35
  self._doc = None
34
36
 
35
37
  if isinstance(sim, Path):
@@ -38,6 +40,7 @@ class SimpleSimGPU_Report():
38
40
  except Exception as e:
39
41
  logging.error(f"Failed to load simulation from path {sim}: {e}")
40
42
  self._sim = None
43
+ self._summary['errors'] = e
41
44
  return
42
45
  elif isinstance(sim, str):
43
46
  try:
@@ -45,6 +48,7 @@ class SimpleSimGPU_Report():
45
48
  except Exception as e:
46
49
  logging.error(f"Failed to load simulation from string path {sim}: {e}")
47
50
  self._sim = None
51
+ self._summary['errors'] = e
48
52
  return
49
53
  elif not isinstance(sim, SimpleSimulation):
50
54
  try:
@@ -52,12 +56,12 @@ class SimpleSimGPU_Report():
52
56
  except Exception as e:
53
57
  logging.error(f"Failed to set simulation: {e}")
54
58
  self._sim = None
59
+ self._summary['errors'] = e
55
60
  return
56
61
  else:
57
62
  logging.error("Invalid type for simulation. Must be SimpleSimulation, Path, or str.")
58
63
  return
59
64
 
60
- self._summary = {}
61
65
  self._summary['warnings'] = self._summary_warnings()
62
66
  self._summary['errors'] = self._summary_errors()
63
67
 
@@ -272,9 +276,13 @@ class SimpleSimGPU_Report():
272
276
  # Plot the histogram of waterdepth adn add it to the PDF
273
277
  fig, ax = plt.subplots(figsize=(8, 6))
274
278
  # Plot the histogram of water depth
275
- ax.hist(sim.h[sim.h > 0.], bins=100, density=True)
276
- # ax.set_title('Histogram of Water Depth')
277
- ax.set_xlim(0, np.max(sim.h[sim.h > 0.]) * 1.25) # Set xlim to 110% of max value
279
+
280
+ h_min = np.min(sim.h[sim.nap == 1])
281
+ h_max = np.max(sim.h[sim.nap == 1])
282
+
283
+ if h_max > h_min:
284
+ ax.hist(sim.h[sim.h > 0.], bins=100, density=True)
285
+ ax.set_xlim(0, h_max) # Set xlim to 110% of max value
278
286
  ax.set_xlabel('Water Depth [m]')
279
287
  ax.set_ylabel('Frequency')
280
288
 
@@ -301,7 +309,7 @@ class SimpleSimGPU_Report():
301
309
  ax.hist(sim.manning[sim.nap == 1], bins=100, density = True)
302
310
  # ax.set_title('Histogram of Manning Coefficient')
303
311
  ax.set_xlabel('Manning [$\\frac {s} {m^{1/3}} $]')
304
- ax.set_xlim(0, np.max(sim.manning[sim.nap == 1]) * 1.25) # Set xlim to 110% of max value
312
+ ax.set_xlim(0, np.max(sim.manning[sim.nap == 1]) * 1.1) # Set xlim to 110% of max value
305
313
  ax.set_ylabel('Frequency')
306
314
 
307
315
  # set font size of the labels
@@ -771,15 +779,21 @@ class SimpleSimGPU_Report():
771
779
 
772
780
  class SimpleSimGPU_Report_wx(PDFViewer):
773
781
 
774
- def __init__(self, sim:SimpleSimulation | Path | str, **kwargs):
782
+ def __init__(self, sim:SimpleSimulation | Path | str, show:bool=False, **kwargs):
775
783
  """ Initialize the Simple Simulation GPU Report Viewer """
776
784
 
785
+ mpl.use('Agg') # Use a non-interactive backend for matplotlib
786
+
777
787
  super(SimpleSimGPU_Report_wx, self).__init__(None, **kwargs)
778
788
 
779
789
  self._report = SimpleSimGPU_Report(sim, **kwargs)
780
790
 
781
791
  if self._report._sim is None:
782
792
  logging.error("No simulation data available to create report.")
793
+ dlg = wx.MessageDialog(self, "No simulation data available to create report.\n\nPlease check the errors in the logs.",
794
+ "Error", wx.OK | wx.ICON_ERROR)
795
+ dlg.ShowModal()
796
+ dlg.Destroy()
783
797
  return
784
798
 
785
799
  self._report.create_report()
@@ -798,6 +812,11 @@ class SimpleSimGPU_Report_wx(PDFViewer):
798
812
 
799
813
  self.Bind(wx.EVT_CLOSE, self.on_close)
800
814
 
815
+ if show:
816
+ self.Show()
817
+
818
+ mpl.use('WxAgg') # Reset matplotlib to use the WxAgg backend for other plots
819
+
801
820
  def on_close(self, event):
802
821
  """ Handle the close event of the frame """
803
822
 
@@ -110,6 +110,8 @@ class GPU_2D_file(Enum):
110
110
  MANNING = _gpu_file('manning' , np.float32, GPU_2D_file_extensions.NPY.value)
111
111
  COMPUTATION_MASK = _gpu_file('nap' , np.uint8 , GPU_2D_file_extensions.NPY.value)
112
112
  INFILTRATION = _gpu_file('infiltration_zones', np.int32, GPU_2D_file_extensions.NPY.value)
113
+ ROOF = _gpu_file('bridge_roof' , np.float32, GPU_2D_file_extensions.NPY.value)
114
+ # DECK = _gpu_file('bridge_deck' , np.float32, GPU_2D_file_extensions.NPY.value)
113
115
 
114
116
  # répertoire de sortie des simulations GPU
115
117
  RESULT_DIR = 'simul_gpu_results'
@@ -240,6 +242,8 @@ class Config_Manager_2D_GPU:
240
242
  self._epsilon = 0.01
241
243
  self._filter_independent = True
242
244
 
245
+ self._active_simulation = None
246
+
243
247
  self.load_data()
244
248
 
245
249
  def find_wolfgpu(self):
@@ -559,15 +563,172 @@ class Config_Manager_2D_GPU:
559
563
 
560
564
  logging.info(_('Hydrographs...'))
561
565
  hydro = self.get_hydrographs()
562
- nb = hydro[0].data.shape[1]
566
+ if len(hydro) == 0:
567
+ logging.info(_('No hydrographs found !'))
568
+ log += _('No hydrographs found !') + '\n'
569
+ else:
570
+ try:
571
+ nb = hydro[0].data.shape[1]
572
+
573
+ logging.info(_('Number of hydrographs : {}'.format(len(hydro))))
574
+ for curhydro in hydro:
575
+ if curhydro.data.shape[1] != nb:
576
+ loclog = _('Bad number of columns for {} !'.format(curhydro[0]._filename))
577
+ log += loclog + '\n'
578
+ logging.warning(loclog)
579
+ except Exception as e:
580
+ logging.error(_('Error while checking hydrographs: {}').format(e))
581
+ log += _('Error while checking hydrographs: {}').format(e) + '\n'
582
+
583
+ return log
584
+
585
+ def check_one_simulation(self) -> str:
586
+ """
587
+ Check consistency of one simulation
588
+
589
+ """
590
+
591
+ if self._active_simulation is None:
592
+ logging.error(_('No active simulation !'))
593
+ return _('No active simulation !')
594
+
595
+ logging.info(_('Checking consistency of one simulation...\n'))
596
+
597
+ logging.info(_('NPY files...'))
598
+ numpyfiles = self._active_simulation[GPU_2D_file_extensions.NPY.value]
599
+ numpynames = [file.name.lower() for file in numpyfiles]
600
+
601
+ logging.info(_('Number of numpy files : {}'.format(len(numpyfiles))))
602
+
603
+ if self._header.nbx == 0 or self._header.nby == 0:
604
+ logging.error(_('No header found !'))
605
+ logging.info(_('Trying to get header from first numpy file...'))
606
+ try:
607
+ wa = WolfArray(numpyfiles[0])
608
+ self._header = wa.get_header()
609
+ except Exception as e:
610
+ logging.error(_('Error while getting header from first numpy file: {}').format(e))
611
+ return _('Error while getting header from first numpy file: {}').format(e)
612
+
613
+ log = ''
614
+ for curnpy in numpyfiles:
615
+
616
+ # test if the shape of the numpy file is the same as the tif file
617
+ # using memmap to avoid loading the whole array in memory -> faster
618
+ arr = np.lib.format.open_memmap(curnpy, mode='r')
563
619
 
564
- logging.info(_('Number of hydrographs : {}'.format(len(hydro))))
565
- for curhydro in hydro:
566
- if curhydro.data.shape[1] != nb:
567
- loclog = _('Bad number of columns for {} !'.format(curhydro[0]._filename))
620
+ if arr.shape != (self._header.nbx, self._header.nby):
621
+ loclog = _('Bad shape for {} !'.format(curnpy))
568
622
  log += loclog + '\n'
569
623
  logging.warning(loclog)
570
624
 
625
+ del(arr)
626
+
627
+ logging.info(_('Hydrographs...'))
628
+ hydro = self.get_hydrographs()
629
+ if len(hydro) == 0:
630
+ logging.info(_('No hydrographs found !'))
631
+ log += _('No hydrographs found !') + '\n'
632
+ else:
633
+ try:
634
+ nb = hydro[0].data.shape[1]
635
+
636
+ logging.info(_('Number of hydrographs : {}'.format(len(hydro))))
637
+ for curhydro in hydro:
638
+ if curhydro.data.shape[1] != nb:
639
+ loclog = _('Bad number of columns for {} !'.format(curhydro[0]._filename))
640
+ log += loclog + '\n'
641
+ logging.warning(loclog)
642
+ except Exception as e:
643
+ logging.error(_('Error while checking hydrographs: {}').format(e))
644
+ log += _('Error while checking hydrographs: {}').format(e) + '\n'
645
+
646
+ for curfile in GPU_2D_file:
647
+ if curfile.value.extension == '.npy':
648
+ if curfile.value.name + curfile.value.extension not in numpynames:
649
+ loclog = _('Missing file {} !').format(curfile.value.name + curfile.value.extension)
650
+ log += loclog + '\n'
651
+ logging.warning(loclog)
652
+ else:
653
+ try:
654
+ wa = WolfArray(numpyfiles[numpynames.index(curfile.value.name + curfile.value.extension)])
655
+ if wa.dtype != curfile.value.type:
656
+ loclog = _('Bad dtype for {} ! Expected {}, got {}').format(
657
+ curfile.value.name + curfile.value.extension,
658
+ curfile.value.type, wa.dtype)
659
+ log += loclog + '\n'
660
+ logging.warning(loclog)
661
+ del(wa)
662
+ except Exception as e:
663
+ loclog = _('Error while checking {}: {}').format(
664
+ curfile.value.name + curfile.value.extension, e)
665
+ log += loclog + '\n'
666
+ logging.error(loclog)
667
+
668
+ if 'nap.npy' in numpynames:
669
+ nap = WolfArray(numpyfiles[numpynames.index('nap.npy')])
670
+ else:
671
+ nap = None
672
+ logging.error(_('No nap file found !'))
673
+ log += _('No nap file found !') + '\n'
674
+
675
+ if nap is not None:
676
+ if 'infiltration_zones.npy' in numpynames:
677
+ # Check if infiltration zones are consistent
678
+ infil_zones = WolfArray(numpyfiles[numpynames.index('infiltration_zones.npy')])
679
+
680
+ if np.any(infil_zones.array[nap.array == 0] != 0):
681
+ loclog = _('Infiltration zones are not consistent with the nap file !')
682
+ log += loclog + '\n'
683
+
684
+ # find all non zero infiltration zones
685
+ non_zero_infil_zones = list(set(np.unique(infil_zones.array[nap.array == 1])))
686
+ if len(non_zero_infil_zones) == 0:
687
+ loclog = _('No infiltration zones found !')
688
+ log += loclog + '\n'
689
+ else:
690
+ if non_zero_infil_zones[0] == 0:
691
+ #pop
692
+ non_zero_infil_zones.pop(0)
693
+ if len(non_zero_infil_zones) == 0:
694
+ loclog = _('No infiltration zones found !')
695
+ log += loclog + '\n'
696
+
697
+ elif non_zero_infil_zones[0] !=1:
698
+ loclog = _('Infiltration zones should start at 1 ! Found {} instead.').format(non_zero_infil_zones[0])
699
+ log += loclog + '\n'
700
+ logging.warning(loclog)
701
+
702
+ # increments
703
+ if not all([non_zero_infil_zones[i] - non_zero_infil_zones[i-1] == 1 for i in range(1, len(non_zero_infil_zones))]):
704
+ loclog = _('Infiltration zones are not consecutive !')
705
+ log += loclog + '\n'
706
+ logging.warning(loclog)
707
+ del(infil_zones)
708
+
709
+ if 'bathymetry.npy' in numpynames:
710
+ # Check if infiltration zones are consistent
711
+ bath = WolfArray(numpyfiles[numpynames.index('bathymetry.npy')])
712
+
713
+ if np.any(bath.array[nap.array == 0] != 99999.):
714
+ loclog = _("Some bathymetry's cells are different of 99999 outside the computation domain !\n")
715
+ loclog += _('This may lead to unexpected results !')
716
+ log += loclog + '\n'
717
+
718
+ del(bath)
719
+
720
+ for file in ['h.npy', 'qx.npy', 'qy.npy']:
721
+ if file in numpynames:
722
+ # Check if infiltration zones are consistent
723
+ bath = WolfArray(numpyfiles[numpynames.index(file)])
724
+
725
+ if np.any(bath.array[nap.array == 0] != 0.):
726
+ loclog = _("Some cells are different of 0.0 outside the computation domain in {}!\n").format(file)
727
+ loclog += _('This may lead to unexpected results !')
728
+ log += loclog + '\n'
729
+
730
+ del(bath)
731
+
571
732
  return log
572
733
 
573
734
  # Analyze files
@@ -1188,6 +1349,22 @@ class Config_Manager_2D_GPU:
1188
1349
  else:
1189
1350
  logging.error(_("No 'bathymetry.tif' file found in the root directory !"))
1190
1351
 
1352
+ def create_void_dtm(self):
1353
+ """ create void dtm file """
1354
+ if (self.workingdir / 'bathymetry.tif').exists():
1355
+ locheader = self.get_header()
1356
+ dtm = WolfArray(srcheader=locheader, whichtype= WOLF_ARRAY_FULL_SINGLE)
1357
+ dtm.array.data[:,:] = 99999.
1358
+ dtm.nullvalue = 99999.
1359
+ dtm.write_all(str(self.workingdir / 'dtm.tif'))
1360
+
1361
+ if (self.workingdir / 'dtm.tif').exists():
1362
+ logging.info(_('dtm.tif created and set to 99999. ! -- Please edit it !'))
1363
+ else:
1364
+ logging.error(_("dtm.tif not created ! -- Does 'bathymetry.tif' or any '.tif' file exist in the root directory ?"))
1365
+ else:
1366
+ logging.error(_("No 'bathymetry.tif' file found in the root directory !"))
1367
+
1191
1368
  def create_void_roof(self):
1192
1369
  """ create void roof file """
1193
1370
 
@@ -1774,10 +1951,18 @@ class UI_Manager_2D_GPU():
1774
1951
  self._reload.Bind(wx.EVT_BUTTON,self.onupdate_structure)
1775
1952
  self._reload.SetToolTip(_('reScan the directory and reload the entire structure'))
1776
1953
 
1954
+ tif_sizer = wx.BoxSizer(wx.HORIZONTAL)
1777
1955
  self._create_void_infil = wx.Button(self._frame,label = _('Create .tif infiltration zones'))
1778
1956
  self._create_void_infil.Bind(wx.EVT_BUTTON,self.oncreate_void_infil)
1779
1957
  self._create_void_infil.SetToolTip(_('Create a void infiltration zones file based on bathymetry.tif'))
1780
1958
 
1959
+ self._create_dtm = wx.Button(self._frame,label = _('Create .tif DTM'))
1960
+ self._create_dtm.Bind(wx.EVT_BUTTON,self.oncreate_dtm)
1961
+ self._create_dtm.SetToolTip(_('Create a DTM file based on bathymetry.tif\n\n - bathymetry.tif must be present in the root directory'))
1962
+
1963
+ tif_sizer.Add(self._create_void_infil, 1, wx.EXPAND)
1964
+ tif_sizer.Add(self._create_dtm, 1, wx.EXPAND)
1965
+
1781
1966
  bridge_sizer = wx.BoxSizer(wx.HORIZONTAL)
1782
1967
  self._create_void_roof = wx.Button(self._frame,label = _('Create .tif bridge/culvert roof elevation'))
1783
1968
  self._create_void_roof.Bind(wx.EVT_BUTTON,self.oncreate_void_roof)
@@ -1818,6 +2003,10 @@ class UI_Manager_2D_GPU():
1818
2003
  self._checkconsistency.Bind(wx.EVT_BUTTON,self.oncheck_consistency)
1819
2004
  self._checkconsistency.SetToolTip(_('Check consistency of the scenario\n\n - bathymetry.tif\n - manning.tif\n - infiltration.tif\n - hydrographs\n - initial conditions\n - boundary conditions\n - scripts'))
1820
2005
 
2006
+ self._checkonesimulation = wx.Button(self._frame,label = _('Check one simulation'))
2007
+ self._checkonesimulation.Bind(wx.EVT_BUTTON,self.oncheck_one_simulation)
2008
+ self._checkonesimulation.SetToolTip(_('Check consistency of one simulation'))
2009
+
1821
2010
  self._createsim = wx.Button(self._frame,label = _('Create simulation(s)'))
1822
2011
  self._createsim.Bind(wx.EVT_BUTTON,self.oncreate_simulation)
1823
2012
  self._createsim.SetToolTip(_('Create simulation(s) from selected hydrographs'))
@@ -1861,14 +2050,19 @@ class UI_Manager_2D_GPU():
1861
2050
 
1862
2051
  # buttons -> sizer
1863
2052
  sizer_buttons.Add(self._reload,1,wx.EXPAND)
1864
- sizer_buttons.Add(self._create_void_infil,1,wx.EXPAND)
2053
+ sizer_buttons.Add(tif_sizer,1,wx.EXPAND)
1865
2054
  sizer_buttons.Add(bridge_sizer,1,wx.EXPAND)
1866
2055
  sizer_buttons.Add(self._create_void_scripts,1,wx.EXPAND)
1867
2056
  sizer_buttons.Add(self._check_prefix,1,wx.EXPAND)
1868
2057
  sizer_buttons.Add(self._create_vrt,1,wx.EXPAND)
1869
2058
  sizer_buttons.Add(self._translate_vrt,1,wx.EXPAND)
1870
2059
  sizer_buttons.Add(self._apply_scripts,1,wx.EXPAND)
1871
- sizer_buttons.Add(self._checkconsistency,1,wx.EXPAND)
2060
+
2061
+ _sizer_check = wx.BoxSizer(wx.HORIZONTAL)
2062
+ _sizer_check.Add(self._checkconsistency, 1, wx.EXPAND)
2063
+ _sizer_check.Add(self._checkonesimulation, 1, wx.EXPAND)
2064
+
2065
+ sizer_buttons.Add(_sizer_check,1,wx.EXPAND)
1872
2066
  sizer_buttons.Add(self._create_vec,1,wx.EXPAND)
1873
2067
  sizer_buttons.Add(self.listsims,1,wx.EXPAND)
1874
2068
  sizer_buttons.Add(self._createsim,1,wx.EXPAND)
@@ -1931,6 +2125,12 @@ class UI_Manager_2D_GPU():
1931
2125
  self._parent.create_void_infil()
1932
2126
  self.reload()
1933
2127
 
2128
+ def oncreate_dtm(self, e:wx.MouseEvent):
2129
+ """ Création d'un fichier DTM vide """
2130
+
2131
+ self._parent.create_void_dtm()
2132
+ self.reload()
2133
+
1934
2134
  def oncreate_void_roof(self, e:wx.MouseEvent):
1935
2135
  """ Création d'un fichier de toit de pont vide """
1936
2136
 
@@ -2122,6 +2322,40 @@ class UI_Manager_2D_GPU():
2122
2322
  else:
2123
2323
  self._txtctrl.WriteText(log)
2124
2324
 
2325
+ def oncheck_one_simulation(self, e:wx.MouseEvent):
2326
+ """ Vérification de la cohérence d'une simulation
2327
+ """
2328
+ self._txtctrl.Clear()
2329
+ log = self._parent.check_one_simulation()
2330
+ if log == '':
2331
+ self._txtctrl.WriteText("\n\n".join([_("All seems fine !")]))
2332
+ else:
2333
+ self._txtctrl.WriteText("\n\n".join([log]))
2334
+
2335
+ # Info on Python Environment and wolfgpu Path and version
2336
+ # -------------------------------------------------------
2337
+
2338
+ import sys
2339
+ # Python Environment
2340
+ self._txtctrl.write(_('\nPython Environment\n'))
2341
+ self._txtctrl.write('-------------------\n')
2342
+ self._txtctrl.write('Python version : {}\n'.format(sys.version))
2343
+ self._txtctrl.write('Python path : {}\n'.format(sys.executable))
2344
+ self._txtctrl.write('Python version info : {}\n'.format(sys.version_info))
2345
+
2346
+ # Test if wolfgpu.exe exists in script directory
2347
+ # wolfgpu Path and version
2348
+ self._txtctrl.write('\nWolfgpu Path and version\n')
2349
+ self._txtctrl.write('------------------------\n')
2350
+
2351
+ wolfgpu = self._parent.wolfgpu
2352
+ if wolfgpu.exists():
2353
+ self._txtctrl.write('Wolfgpu.exe found in : {}\n'.format(self._parent.wolfgpu.parent))
2354
+ else:
2355
+ self._txtctrl.write('Wolfgpu.exe not found !\n')
2356
+ self._parent.wolfgpu = None
2357
+
2358
+
2125
2359
  def oncheck_consistency(self,e:wx.MouseEvent):
2126
2360
  """ Vérification de la cohérence des fichiers """
2127
2361
 
@@ -2422,10 +2656,12 @@ class UI_Manager_2D_GPU():
2422
2656
  self._txtctrl.SetBackgroundColour(wx.WHITE)
2423
2657
  self._txtctrl.SetForegroundColour(wx.BLACK)
2424
2658
 
2659
+ self._parent._active_simulation = None
2425
2660
  if isinstance(mydata, dict):
2426
2661
  self._txtctrl.write(_('Yous have selected : {}\n\n'.format(str(mydata['path']))))
2427
2662
 
2428
2663
  if mydata[IS_SIMUL]:
2664
+ self._parent._active_simulation = mydata
2429
2665
  self._txtctrl.write(_('GPU SIMULATION\n\n'))
2430
2666
 
2431
2667
  if ctrl and not shift and not alt:
@@ -211,6 +211,8 @@ class Wolf_MultipleSelection(wx.Dialog):
211
211
  assert len(self.delete_if_transfer) == len(values_dict), "delete_if_transfer must be a list of len(values_dict)"
212
212
  for i, values in enumerate(values_dict.values()):
213
213
  assert isinstance(self.delete_if_transfer[i], bool), "delete_if_transfer must be a list of boolean"
214
+ else:
215
+ self.delete_if_transfer = None
214
216
 
215
217
  super().__init__(parent, title=title)
216
218
 
@@ -339,6 +341,145 @@ class Wolf_MultipleSelection(wx.Dialog):
339
341
  if self.callback is not None:
340
342
  self.callback(self)
341
343
 
344
+ class Wolf_CompareArrays_Selection(Wolf_MultipleSelection):
345
+ """
346
+ Dialog with multiple 'collapsiblepanes' containing 2 lists to select multiple items
347
+ This class is a specific case of Wolf_MultipleSelection for comparing arrays
348
+ """
349
+ def __init__(self,
350
+ parent,
351
+ title,
352
+ values_dict:dict,
353
+ callback = None,
354
+ info:str='',
355
+ cmdApply:bool=False,
356
+ styles = wx.LB_EXTENDED,
357
+ destroyOK = False,
358
+ **kwargs):
359
+ """
360
+ :param parent : wx.Window
361
+ :param title : str - title of the frame
362
+ :param values_dict : dict - {'label1':[item1, item2, ...], 'label2':[item1, item2, ...], ...}
363
+ :param callback : function - callback function when OK or Apply button is pressed
364
+ :param info : str - information to display upper the collapsiblepanes
365
+ :param cmdApply : bool - if True, Apply button is displayed
366
+ :param styles : wx.ListBox styles - wx constant or list of wx constants
367
+ :param kwargs : dict - other arguments for wx.Frame (example : max_selected_items=[1, 2, 3], delete_if_transfer=[True, False, True])
368
+ """
369
+ super().__init__(parent=parent,
370
+ title=title,
371
+ values_dict=values_dict,
372
+ callback=callback,
373
+ info=info,
374
+ cmdApply=cmdApply,
375
+ styles=styles,
376
+ destroyOK=destroyOK,
377
+ max_selected_items=[1]*len(values_dict),
378
+ delete_if_transfer=[True]*len(values_dict),
379
+ **kwargs)
380
+
381
+ # add some UI specific to the compare arrays selection
382
+
383
+ # textbox for a threshold value
384
+ self.threshold_label = wx.StaticText(self, label="Threshold value [m]:")
385
+ self.threshold_value = wx.TextCtrl(self, value="0.01", style=wx.TE_PROCESS_ENTER | wx.TE_CENTER)
386
+ self.threshold_value.Bind(wx.EVT_TEXT_ENTER, self._on_threshold_enter)
387
+ self.threshold_value.Bind(wx.EVT_KILL_FOCUS, self._on_threshold_enter)
388
+ self.threshold_value.SetToolTip("Enter a threshold value - All differences below this threshold will be considered as equal")
389
+ # add the threshold label and value to the sizer
390
+ sizer = self.GetSizer()
391
+
392
+ sizer_threshold = wx.BoxSizer(wx.HORIZONTAL)
393
+ sizer_threshold.Add(self.threshold_label, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
394
+ sizer_threshold.Add(self.threshold_value, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
395
+ sizer.Add(sizer_threshold, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
396
+
397
+ # textbox for a minimal area value
398
+ self.min_area_label = wx.StaticText(self, label="Minimal area value [m²]:")
399
+ self.min_area_value = wx.TextCtrl(self, value="2.0", style=wx.TE_PROCESS_ENTER | wx.TE_CENTER)
400
+ self.min_area_value.Bind(wx.EVT_TEXT_ENTER, self._on_min_area_enter)
401
+ self.min_area_value.Bind(wx.EVT_KILL_FOCUS, self._on_min_area_enter)
402
+ self.min_area_value.SetToolTip("Enter a minimal area value - All patches below this area will be ignored")
403
+ # add the minimal area label and value to the sizer
404
+ sizer_min_area = wx.BoxSizer(wx.HORIZONTAL)
405
+ sizer_min_area.Add(self.min_area_label, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
406
+ sizer_min_area.Add(self.min_area_value, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
407
+ sizer.Add(sizer_min_area, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
408
+ # Maximum patches to include in the report
409
+ self.max_patches_label = wx.StaticText(self, label="Maximum patches to include in the report:")
410
+ self.max_patches_value = wx.TextCtrl(self, value="10", style=wx.TE_PROCESS_ENTER | wx.TE_CENTER)
411
+ self.max_patches_value.Bind(wx.EVT_TEXT_ENTER, self._on_max_patches_enter)
412
+ self.max_patches_value.Bind(wx.EVT_KILL_FOCUS, self._on_max_patches_enter)
413
+ self.max_patches_value.SetToolTip("Enter a maximum number of patches to include in the report - If more patches are found, only the first ones will be included")
414
+ # add the maximum patches label and value to the sizer
415
+ sizer_max_patches = wx.BoxSizer(wx.HORIZONTAL)
416
+ sizer_max_patches.Add(self.max_patches_label, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
417
+ sizer_max_patches.Add(self.max_patches_value, proportion=1, flag= wx.EXPAND | wx.ALL, border=5)
418
+ sizer.Add(sizer_max_patches, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
419
+ # add the sizer to the main sizer
420
+ self.SetSizer(sizer)
421
+ self.Fit()
422
+ self.Layout()
423
+
424
+
425
+ def _on_threshold_enter(self, event):
426
+ """ Callback when the threshold value is changed """
427
+ try:
428
+ value = float(self.threshold_value.GetValue())
429
+ if value < 0:
430
+ self.threshold_value.SetValue("0.01")
431
+ except ValueError as e:
432
+ wx.MessageBox(f"Invalid threshold value: {e}", "Error", wx.OK | wx.ICON_ERROR)
433
+ self.threshold_value.SetValue("0.01")
434
+ event.Skip()
435
+
436
+ def _on_min_area_enter(self, event):
437
+ """ Callback when the minimal area value is changed """
438
+ try:
439
+ value = float(self.min_area_value.GetValue())
440
+ if value < 0:
441
+ self.min_area_value.SetValue("2.0")
442
+ except ValueError as e:
443
+ wx.MessageBox(f"Invalid minimal area value: {e}", "Error", wx.OK | wx.ICON_ERROR)
444
+ self.min_area_value.SetValue("2.0")
445
+ event.Skip()
446
+
447
+ def _on_max_patches_enter(self, event):
448
+ """ Callback when the maximum patches value is changed """
449
+ try:
450
+ value = int(self.max_patches_value.GetValue())
451
+ if value < 1:
452
+ self.max_patches_value.SetValue("10")
453
+ except ValueError as e:
454
+ wx.MessageBox(f"Invalid maximum patches value: {e}", "Error", wx.OK | wx.ICON_ERROR)
455
+ self.max_patches_value.SetValue("10")
456
+ event.Skip()
457
+
458
+ def get_threshold(self):
459
+ """ Get the threshold value """
460
+ try:
461
+ return float(self.threshold_value.GetValue())
462
+ except ValueError:
463
+ wx.MessageBox("Invalid threshold value", "Error", wx.OK | wx.ICON_ERROR)
464
+ return 0.01
465
+
466
+ def get_min_area(self):
467
+ """ Get the minimal area value """
468
+ try:
469
+ return float(self.min_area_value.GetValue())
470
+ except ValueError:
471
+ wx.MessageBox("Invalid minimal area value", "Error", wx.OK | wx.ICON_ERROR)
472
+ return 2.0
473
+
474
+ def get_max_patches(self):
475
+ """ Get the maximum patches value """
476
+ try:
477
+ return int(self.max_patches_value.GetValue())
478
+ except ValueError:
479
+ wx.MessageBox("Invalid maximum patches value", "Error", wx.OK | wx.ICON_ERROR)
480
+ return 10
481
+
482
+
342
483
  if __name__ == "__main__":
343
484
  # Utilisation de la classe Wolf_MultipleSelection
344
485
  app = wx.App()
@@ -351,6 +492,17 @@ if __name__ == "__main__":
351
492
  callback=None,
352
493
  styles=[wx.LB_SINGLE, wx.LB_EXTENDED, wx.LB_EXTENDED],
353
494
  max_selected_items=[1, -1, -1],)
354
- frame.ShowModal()
495
+
496
+ fram2 = Wolf_CompareArrays_Selection(None,
497
+ title="Exemple de Wolf_CompareArrays_Selection",
498
+ values_dict={'domain':["Item 1", "Item 2", "Item 3", "Item 4"],
499
+ 'u':["Item 3", "Item 4", "Item 5", "Item 6"],
500
+ 'v':["Item v1", "Item v2", "Item v3", "Item v4"]},
501
+ info="You can define : \n - the domain\n - u\n -v",
502
+ callback=None,
503
+ styles=[wx.LB_SINGLE, wx.LB_SINGLE, wx.LB_SINGLE],
504
+ destroyOK=True)
505
+
506
+ fram2.ShowModal()
355
507
  pass
356
508
  app.MainLoop()