wolfhece 2.1.97__py3-none-any.whl → 2.1.99__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/PyDraw.py CHANGED
@@ -24,6 +24,7 @@ try:
24
24
  import json
25
25
  import glob
26
26
  import traceback
27
+ from datetime import datetime, timedelta
27
28
  except ImportError as e:
28
29
  print(e)
29
30
  raise ImportError("Error importing wxPython, numpy, PIL, json, glob, traceback. Please check your installation.")
@@ -731,6 +732,372 @@ class DragdropFileTarget(wx.FileDropTarget):
731
732
 
732
733
  return True
733
734
 
735
+
736
+ class Sim_Explorer(wx.Frame):
737
+
738
+ def __init__(self, parent, title, mapviewer:"WolfMapViewer", sim:Wolfresults_2D):
739
+
740
+ super(Sim_Explorer, self).__init__(parent, title=title, size=(150, 250), style = wx.DEFAULT_FRAME_STYLE & ~ (wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX))
741
+
742
+ self._panel = wx.Panel(self)
743
+
744
+ self.mapviewer = mapviewer
745
+ self.active_res2d:Wolfresults_2D = sim
746
+
747
+ main_sizer = wx.BoxSizer(wx.HORIZONTAL)
748
+
749
+ left_bar = wx.BoxSizer(wx.VERTICAL)
750
+ right_bar = wx.BoxSizer(wx.VERTICAL)
751
+
752
+ self._all_times_steps = self.active_res2d.get_times_steps()
753
+
754
+ # Right bar
755
+ # ---------
756
+
757
+ # Slider
758
+ self._slider_steps = wx.Slider(self._panel, minValue=1, maxValue=sim.get_nbresults(), style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_MIN_MAX_LABELS | wx.SL_LABELS)
759
+ self._slider_steps.Bind(wx.EVT_SLIDER, self.OnSliderSteps)
760
+ right_bar.Add(self._slider_steps, 1, wx.EXPAND | wx.ALL, 2)
761
+
762
+ # Explore by index
763
+ self._label_idx = wx.StaticText(self._panel, label=_('Index'))
764
+ right_bar.Add(self._label_idx, 1, wx.EXPAND | wx.ALL, 2)
765
+
766
+ self._step_idx = wx.ListBox(self._panel, choices=[str(i) for i in range(1, sim.get_nbresults()+1)], style=wx.LB_SINGLE)
767
+ self._step_idx.Bind(wx.EVT_LISTBOX, self.OnSelectIdxStep)
768
+ right_bar.Add(self._step_idx, 1, wx.EXPAND | wx.ALL, 5)
769
+
770
+ # Explore by time
771
+ self._label_time = wx.StaticText(self._panel, label=_('Time [s]'))
772
+ right_bar.Add(self._label_time, 1, wx.EXPAND | wx.ALL, 2)
773
+
774
+ _now = datetime.now()
775
+ self._starting_date = datetime(year=_now.year, month=_now.month, day=_now.day, hour=0, minute=0, second=0)
776
+ self._texttime = wx.TextCtrl(self._panel, value=self._starting_date.strftime('%Y-%m-%d %H:%M:%S'))
777
+ right_bar.Add(self._texttime, 1, wx.EXPAND | wx.ALL, 5)
778
+ self._texttime.Bind(wx.EVT_TEXT, self.OnTextTime)
779
+
780
+ self._step_time = wx.ListBox(self._panel, choices=['{:.3f} - {}'.format(i, datetime.strftime(self._starting_date + timedelta(seconds=i), '%Y-%m-%d %H:%M:%S')) for i in self._all_times_steps[0]], style=wx.LB_SINGLE)
781
+ self._step_time.Bind(wx.EVT_LISTBOX, self.OnSelectNumStep)
782
+ right_bar.Add(self._step_time, 1, wx.EXPAND | wx.ALL, 5)
783
+
784
+ # Explore by time step
785
+ self._label_steps = wx.StaticText(self._panel, label=_('Time step [-]'))
786
+ right_bar.Add(self._label_steps, 1, wx.EXPAND | wx.ALL, 2)
787
+
788
+ self._step_num = wx.ListBox(self._panel, choices=[str(i) for i in self._all_times_steps[1]], style=wx.LB_SINGLE)
789
+ self._step_num.Bind(wx.EVT_LISTBOX, self.OnSelectCurTime)
790
+ right_bar.Add(self._step_num, 1, wx.EXPAND | wx.ALL, 5)
791
+
792
+ # Left bar
793
+ # --------
794
+
795
+ # Apply selected step
796
+ self._cmd_apply = wx.Button(self._panel, wx.ID_APPLY, _('Apply'))
797
+ self._cmd_apply.SetToolTip(_('Apply the selected parameters to the map'))
798
+ self._cmd_apply.Bind(wx.EVT_BUTTON, self.OnApply)
799
+ left_bar.Add(self._cmd_apply, 1, wx.EXPAND | wx.ALL, 5)
800
+
801
+ # Update listbox from files on disk
802
+ self._cmd_update = wx.Button(self._panel, wx.ID_REFRESH, _('Update'))
803
+ self._cmd_update.SetToolTip(_('Update the list of available results based on the files on disk'))
804
+ self._cmd_update.Bind(wx.EVT_BUTTON, self.OnUpdate)
805
+ left_bar.Add(self._cmd_update, 1, wx.EXPAND | wx.ALL, 5)
806
+
807
+ #Plot
808
+ self._cmd_plot = wx.Button(self._panel, wx.ID_PREVIEW, _('Plot simulation informations'))
809
+ self._cmd_plot.SetToolTip(_('Plot synthesis of the simulation (computation time, time step, clock time, mostly dry mesh...)'))
810
+ self._cmd_plot.Bind(wx.EVT_BUTTON, self.OnPlot)
811
+ left_bar.Add(self._cmd_plot, 1, wx.EXPAND | wx.ALL, 5)
812
+
813
+ # Next step
814
+ self._cmd_next = wx.Button(self._panel, wx.ID_FORWARD, _('Next'))
815
+ self._cmd_next.SetToolTip(_('Go to the next step -- using the selected mode'))
816
+ self._cmd_next.Bind(wx.EVT_BUTTON, self.OnNext)
817
+ left_bar.Add(self._cmd_next, 1, wx.EXPAND | wx.ALL, 5)
818
+
819
+ # Previous step
820
+ self._cmd_prev = wx.Button(self._panel, wx.ID_BACKWARD, _('Previous'))
821
+ self._cmd_prev.SetToolTip(_('Go to the previous step -- using the selected mode'))
822
+ self._cmd_prev.Bind(wx.EVT_BUTTON, self.OnPrev)
823
+ left_bar.Add(self._cmd_prev, 1, wx.EXPAND | wx.ALL, 5)
824
+
825
+ # Check Mode movement
826
+ self._mode = wx.ListBox(self._panel, choices=['by time [s]', 'by time [hour]', 'by index', 'by time step'], style=wx.LB_SINGLE)
827
+ self._mode.SetToolTip(_('Select the mode to move through the simulation'))
828
+ self._mode.SetSelection(2)
829
+ self._interval = wx.TextCtrl(self._panel, value='1', style=wx.ALIGN_CENTER_HORIZONTAL)
830
+ self._interval.SetToolTip(_('Interval for the mode selected -- unit depends on the mode'))
831
+ self._interval.Bind(wx.EVT_TEXT, self.OnInterval)
832
+
833
+ left_bar.Add(self._mode, 1, wx.EXPAND | wx.ALL, 5)
834
+ left_bar.Add(self._interval, 0, wx.EXPAND | wx.ALL, 5)
835
+
836
+ self.Bind(wx.EVT_CLOSE, self.OnClose)
837
+
838
+ main_sizer.Add(left_bar, 1, wx.EXPAND | wx.ALL, 2)
839
+ main_sizer.Add(right_bar, 1, wx.EXPAND | wx.ALL, 2)
840
+
841
+ self._panel.SetSizer(main_sizer)
842
+ self._panel.SetAutoLayout(True)
843
+
844
+ self.MinSize = (450, 500)
845
+
846
+ self.Fit()
847
+ self.Show()
848
+
849
+ self.SetIcon(wx.Icon(str(Path(__file__).parent / "apps/wolf_logo2.bmp")))
850
+
851
+ self._set_all(0)
852
+
853
+ def OnPlot(self, event):
854
+ """ Create a scatter plot of all steps.
855
+
856
+ Major x_axis is time in seconds, Minor X-axis is time by date.
857
+
858
+ Plots:
859
+ - Computation time step (Dt)
860
+ - Computation steps (N)
861
+ - Clock time (s)
862
+ - Mostly dry mesh (N)
863
+
864
+ """
865
+
866
+ main_x = self._all_times_steps[0]
867
+ second_x = [self._starting_date + timedelta(seconds=i) for i in main_x]
868
+
869
+ if isinstance(self.active_res2d, wolfres2DGPU):
870
+
871
+ ax:list[Axes]
872
+ fig, ax= plt.subplots(5, 1, figsize=(10, 8))
873
+
874
+ ax[0].plot(main_x, self._all_times_steps[1], 'o-')
875
+ ax[0].set_ylabel(_('Computation\ntime step (N)'), fontsize=8)
876
+ ax[0].ticklabel_format(axis='y', style='sci', scilimits=(0,0))
877
+
878
+ ax[0].grid(which='both')
879
+ ax[0].set_xticks(main_x)
880
+ ax[0].set_xticklabels([])
881
+
882
+ secax:Axes = ax[0].secondary_xaxis('top')
883
+ secax.set_xlabel(_('Real date\n[Y-M-D H:M:S]'), fontsize=8)
884
+ secax.set_xticks(main_x)
885
+ secax.set_xticklabels([datetime.strftime(i, '%Y-%m-%d %H:%M') for i in second_x], fontsize=8)
886
+ secax.tick_params(axis='x', rotation=30)
887
+
888
+ ax[1].plot(main_x, self.active_res2d.all_dt, 'o-')
889
+ ax[1].set_ylabel(_('$\Delta t$ [s]'), fontsize=8)
890
+ ax[1].grid(which='both')
891
+ ax[1].set_xticks(main_x)
892
+ ax[1].set_xticklabels([])
893
+ ax[1].ticklabel_format(axis='y', style='sci', scilimits=(0,0))
894
+
895
+ ctime = self.active_res2d.all_clock_time
896
+ ax[2].plot(main_x, self.active_res2d.all_clock_time, 'o-')
897
+ ax[2].set_ylabel(_('Clock time [s]'), fontsize=8)
898
+ ax[2].grid(which='both')
899
+ ax[2].set_xticks([])
900
+ ax[2].set_xticks(main_x)
901
+ ax[2].set_xticklabels([])
902
+ ax[2].ticklabel_format(axis='y', style='sci', scilimits=(0,0))
903
+
904
+ # Fit a line on the (main_x - clock time) plot
905
+ # This will give a mean acceleration factor
906
+ # The inverse of the slope of the line is the accelaration factor
907
+ # The line is y = slope * x + intercept
908
+ from scipy.stats import linregress
909
+ slope, intercept, r_value, p_value, std_err = linregress(main_x, ctime)
910
+
911
+ # Plot the info on the ax[2]
912
+ msg = _('Acceleration factor:')
913
+ ax[2].text(0.5, 0.5, f'{msg} {1/slope:.2f}', transform=ax[2].transAxes, fontsize=12, verticalalignment='top')
914
+
915
+ ax[3].plot(main_x, self.active_res2d.all_wet_meshes, 'o-', color='blue')
916
+ ax[3].plot(main_x, self.active_res2d.all_mostly_dry_mesh, 'o-', color='green')
917
+ ax[3].set_ylabel(_('Wet and Mostly dry\nmeshes [N]'), fontsize=8)
918
+ ax[3].grid(which='both')
919
+ ax[3].set_xticks(main_x)
920
+ ax[3].set_xlabel([])
921
+ ax[3].ticklabel_format(axis='y', style='sci', scilimits=(0,0))
922
+
923
+ ax[4].plot(main_x, [i/j*100 if j>0 else 0 for i, j in zip(self.active_res2d.all_mostly_dry_mesh, self.active_res2d.all_wet_meshes)], 'o-', color='red')
924
+ ax[4].set_ylabel(_('Wet/Mostly dry\nmeshes [%]'), fontsize=8)
925
+ ax[4].grid(which='both')
926
+ ax[4].set_xticks(main_x)
927
+ ax[4].set_xlabel(_('Simulated time [s]'), fontsize=8)
928
+ ax[4].ticklabel_format(axis='y', style='sci', scilimits=(0,0))
929
+
930
+ fig.suptitle('Simulation {}'.format(self.active_res2d.idx), fontsize=10)
931
+
932
+ fig.tight_layout()
933
+ fig.show()
934
+
935
+ def OnInterval(self, event):
936
+ """ Change the interval """
937
+
938
+ try:
939
+ interv = float(self._interval.GetValue())
940
+ if interv <= 0:
941
+ interv = 1.
942
+ self._interval.SetValue('1')
943
+ except:
944
+ interv = 1
945
+ self._interval.SetValue('1')
946
+
947
+ def _find_next(self, idx:int):
948
+ """ Find the next step based on the mode and interval """
949
+
950
+ mode = int(self._mode.GetSelection())
951
+
952
+ if mode == 0:
953
+ # By time [s]
954
+ next_time = self._all_times_steps[0][idx] + float(self._interval.GetValue())
955
+ diff = [abs(next_time - i) for i in self._all_times_steps[0][idx:]]
956
+ next_idx = diff.index(min(diff)) + idx
957
+
958
+ return next_idx
959
+
960
+ elif mode == 1:
961
+ # By time [hour]
962
+ next_time = self._all_times_steps[0][idx] + float(self._interval.GetValue())*3600
963
+ diff = [abs(next_time - i) for i in self._all_times_steps[0][idx:]]
964
+ next_idx = diff.index(min(diff)) + idx
965
+
966
+ return next_idx
967
+
968
+ elif mode == 2:
969
+ # By index
970
+ next_idx = min(idx + int(self._interval.GetValue()), len(self._all_times_steps[0])-1)
971
+
972
+ return next_idx
973
+
974
+ elif mode == 3:
975
+ # By time step
976
+ next_idx = self._all_times_steps[1].index(self._all_times_steps[1][idx] + int(self._interval.GetValue()))
977
+ diff = [abs(next_idx - i) for i in self._all_times_steps[1][idx:]]
978
+ next_idx = diff.index(min(diff)) + idx
979
+
980
+ return next_idx
981
+
982
+ def _find_prev(self, idx:int):
983
+ """ Find the previous step based on the mode and interval """
984
+
985
+ mode = int(self._mode.GetSelection())
986
+
987
+ if mode == 0:
988
+ # By time [s]
989
+ prev_time = self._all_times_steps[0][idx] - float(self._interval.GetValue())
990
+ diff = [abs(prev_time - i) for i in self._all_times_steps[0][:i]]
991
+ prev_idx = diff.index(min(diff))
992
+
993
+ return prev_idx
994
+
995
+ elif mode == 1:
996
+ # By time [hour]
997
+ prev_time = self._all_times_steps[0][idx] - float(self._interval.GetValue())*3600
998
+ diff = [abs(prev_time - i) for i in self._all_times_steps[0][:i]]
999
+ prev_idx = diff.index(min(diff))
1000
+
1001
+ return prev_idx
1002
+
1003
+ elif mode == 2:
1004
+ # By index
1005
+ prev_idx = max(idx - int(self._interval.GetValue()), 0)
1006
+
1007
+ return prev_idx
1008
+
1009
+ elif mode == 3:
1010
+ # By time step
1011
+ prev_idx = self._all_times_steps[1].index(self._all_times_steps[1][idx] - int(self._interval.GetValue()))
1012
+ diff = [abs(prev_idx - i) for i in self._all_times_steps[1][:i]]
1013
+ prev_idx = diff.index(min(diff))
1014
+
1015
+ return prev_idx
1016
+
1017
+ def OnNext(self, event):
1018
+ """ Go to the next step """
1019
+
1020
+ selected_step = self._slider_steps.GetValue()-1
1021
+ next_idx = self._find_next(selected_step)
1022
+
1023
+ if next_idx != selected_step:
1024
+ self._set_all(next_idx)
1025
+ self.Refresh(next_idx)
1026
+
1027
+ def OnPrev(self, event):
1028
+ """ Go to the previous step """
1029
+
1030
+ selected_step = self._slider_steps.GetValue()-1
1031
+ prev_idx = self._find_prev(selected_step)
1032
+
1033
+ if prev_idx != selected_step:
1034
+ self._set_all(prev_idx)
1035
+ self.Refresh(prev_idx)
1036
+
1037
+ def OnTextTime(self, event):
1038
+ try:
1039
+ self._starting_date = datetime.strptime(self._texttime.GetValue(), '%Y-%m-%d %H:%M:%S')
1040
+ self._step_time.Set(['{:.3f} - {}'.format(i, datetime.strftime(self._starting_date + timedelta(seconds=i), '%Y-%m-%d %H:%M:%S')) for i in self._all_times_steps[0]])
1041
+ except:
1042
+ logging.info('Error while parsing the date')
1043
+ pass
1044
+
1045
+ def OnClose(self, event):
1046
+ """ Close the simulation explorer """
1047
+
1048
+ self.mapviewer._pop_sim_explorer(self.active_res2d)
1049
+ self.Destroy()
1050
+
1051
+ def OnUpdate(self, event):
1052
+ self._update()
1053
+
1054
+ def OnApply(self, event):
1055
+ selected_step = self._slider_steps.GetValue()-1
1056
+
1057
+ self._cmd_apply.SetBackgroundColour(wx.Colour(255, 0, 0)) # Set button color to red
1058
+ self._cmd_apply.Refresh() # Refresh the button to apply the color change
1059
+
1060
+ self.Refresh(selected_step)
1061
+
1062
+ self._cmd_apply.SetBackgroundColour(wx.NullColour) # Reset button color to default
1063
+ self._cmd_apply.Refresh() # Refresh the button to apply the color change
1064
+
1065
+ def _set_all(self, idx:int):
1066
+ self._slider_steps.SetValue(idx+1)
1067
+ self._step_idx.SetSelection(idx)
1068
+ self._step_time.SetSelection(idx)
1069
+ self._step_num.SetSelection(idx)
1070
+
1071
+ def Refresh(self, idx:int):
1072
+ self.active_res2d.read_oneresult(idx)
1073
+ self.active_res2d.set_currentview()
1074
+ self.mapviewer.Refresh()
1075
+
1076
+ def OnSliderSteps(self, event):
1077
+ selected_step = self._slider_steps.GetValue()
1078
+ self._set_all(selected_step-1)
1079
+
1080
+ def OnSelectCurTime(self, event):
1081
+ selected_time = self._step_num.GetSelection()
1082
+ self._set_all(selected_time)
1083
+
1084
+ def OnSelectNumStep(self, event):
1085
+ selected_step = self._step_time.GetSelection()
1086
+ self._set_all(selected_step)
1087
+
1088
+ def OnSelectIdxStep(self, event):
1089
+ selected_step = self._step_idx.GetSelection()
1090
+ self._set_all(selected_step)
1091
+
1092
+ def _update(self):
1093
+ nb = self.active_res2d.get_nbresults()
1094
+ self._all_times_steps = self.active_res2d.get_times_steps()
1095
+
1096
+ self._slider_steps.SetMax(nb)
1097
+ self._step_idx.Set([str(i) for i in range(1,nb+1)])
1098
+ self._step_time.Set(['{:.3f} - {}'.format(i, datetime.strftime(self._starting_date + timedelta(seconds=i), '%Y-%m-%d %H:%M:%S')) for i in self._all_times_steps[0]])
1099
+ self._step_num.Set([str(i) for i in self._all_times_steps[1]])
1100
+
734
1101
  class WolfMapViewer(wx.Frame):
735
1102
  """
736
1103
  Fenêtre de visualisation de données WOLF grâce aux WxWidgets
@@ -751,6 +1118,7 @@ class WolfMapViewer(wx.Frame):
751
1118
  mytiles: list[Tiles]
752
1119
  mypartsystems: list[Particle_system]
753
1120
  myviewers3d:list[Wolf_Viewer3D]
1121
+ sim_explorers: dict[Wolfresults_2D:Sim_Explorer]
754
1122
 
755
1123
  canvas: GLCanvas # canvas OpenGL
756
1124
  context: GLContext # context OpenGL
@@ -1217,6 +1585,14 @@ class WolfMapViewer(wx.Frame):
1217
1585
  # # print(msg)
1218
1586
  # return 0
1219
1587
 
1588
+ @property
1589
+ def viewer_name(self):
1590
+ return self.GetTitle()
1591
+
1592
+ @viewer_name.setter
1593
+ def viewer_name(self, value):
1594
+ self.SetTitle(value)
1595
+
1220
1596
  @property
1221
1597
  def wxlogging(self):
1222
1598
  return self._wxlogging
@@ -1442,6 +1818,10 @@ class WolfMapViewer(wx.Frame):
1442
1818
  if self.menuwolf2d is None:
1443
1819
  self.menuwolf2d = wx.Menu()
1444
1820
 
1821
+ self.menu2d_explore_results = self.menuwolf2d.Append(wx.ID_ANY, _("Explore time/index results"), _("Open a dialog to explore time/index results"))
1822
+
1823
+ self.menuwolf2d.AppendSeparator()
1824
+
1445
1825
  self.menu2d_curentview = self.menuwolf2d.Append(wx.ID_ANY, _("Change current view"), _("Current view"))
1446
1826
  self.menu2d_lastres = self.menuwolf2d.Append(wx.ID_ANY, _("Read last result"), _("Current view"))
1447
1827
  self.menu2d_epsilon = self.menuwolf2d.Append(wx.ID_ANY, _("Set epsilon water depth"), _("Set the epsilon used in the mask"))
@@ -1454,11 +1834,11 @@ class WolfMapViewer(wx.Frame):
1454
1834
  self.menuwolf2d.AppendSeparator()
1455
1835
 
1456
1836
  self.menu2d_dangermap = self.menuwolf2d.Append(wx.ID_ANY, _("Danger map"), _("Compute the danger map"))
1457
- self.menu2d_dangermaph = self.menuwolf2d.Append(wx.ID_ANY, _("Danger map - only h"), _("Compute the danger map"))
1837
+ self.menu2d_dangermaph = self.menuwolf2d.Append(wx.ID_ANY, _("Danger map - only h"), _("Compute the danger map - only waterdepth"))
1458
1838
 
1459
1839
  self.menuwolf2d.AppendSeparator()
1460
1840
 
1461
- self.menu2d_export_as = self.menuwolf2d.Append(wx.ID_ANY, _("Export results as..."), _("Export results as Geotif or Shapefile"))
1841
+ self.menu2d_export_as = self.menuwolf2d.Append(wx.ID_ANY, _("Export results as..."), _("Export results as Geotif, Shapefile or Numpy arrays"))
1462
1842
 
1463
1843
  self.menuwolf2d.AppendSeparator()
1464
1844
  # Possible cache entries will be added after this separator
@@ -1651,6 +2031,41 @@ class WolfMapViewer(wx.Frame):
1651
2031
 
1652
2032
  self.active_array.reset_plot()
1653
2033
 
2034
+ def _add_sim_explorer(self, which:Wolfresults_2D):
2035
+ """ Add a step chooser """
2036
+
2037
+ if which in self.sim_explorers:
2038
+ logging.warning(_('Step chooser already exists for this result'))
2039
+ self.sim_explorers[which].Show()
2040
+ self.sim_explorers[which].Raise()
2041
+ self.sim_explorers[which].SetFocus()
2042
+ self.sim_explorers[which].Center()
2043
+ return
2044
+
2045
+ self.sim_explorers[which] = Sim_Explorer(self, which.idx, self, which)
2046
+ self.sim_explorers[which]._set_all(which.current_result)
2047
+
2048
+ def _pop_sim_explorer(self, which:Wolfresults_2D):
2049
+ """ Pop a step chooser """
2050
+
2051
+ if which in self.sim_explorers:
2052
+ self.sim_explorers.pop(which)
2053
+ logging.debug(_('Pop step chooser for result {}'.format(which.idx)))
2054
+ else:
2055
+ logging.warning(_('No step chooser for this result'))
2056
+
2057
+ def _update_sim_explorer(self, which:Wolfresults_2D = None):
2058
+
2059
+ if which is None:
2060
+ if self.active_res2d is None:
2061
+ logging.warning(_('No active 2D result -- Please activate a 2D result first'))
2062
+ return
2063
+
2064
+ which = self.active_res2d
2065
+
2066
+ if which in self.sim_explorers:
2067
+ self.sim_explorers[which]._set_all(which.current_result)
2068
+
1654
2069
  def Onmenuwolf2d(self, event: wx.MenuEvent):
1655
2070
 
1656
2071
  id = event.GetId()
@@ -1670,6 +2085,13 @@ class WolfMapViewer(wx.Frame):
1670
2085
 
1671
2086
  self.export_results_as()
1672
2087
 
2088
+ elif itemlabel == _("Explore time/index results"):
2089
+ if self.active_res2d is None:
2090
+ logging.warning(_('No active 2D result ! -- Please activate a 2D result first'))
2091
+ return
2092
+
2093
+ self._add_sim_explorer(self.active_res2d)
2094
+
1673
2095
  elif itemlabel == _("Change current view"):
1674
2096
 
1675
2097
  # Change view for results
@@ -2450,6 +2872,16 @@ class WolfMapViewer(wx.Frame):
2450
2872
  else:
2451
2873
  return config[ConfigurationKeys.TICKS_SIZE]
2452
2874
 
2875
+ @property
2876
+ def assembly_mode(self) -> str:
2877
+ """ Return the assembly mode from configs """
2878
+
2879
+ config = self.get_configuration()
2880
+ if config is None:
2881
+ return 'square'
2882
+ else:
2883
+ return config[ConfigurationKeys.ASSEMBLY_IMAGES]
2884
+
2453
2885
  @property
2454
2886
  def ticks_bounds(self) -> bool:
2455
2887
  """ Return the ticks bounds from configs """
@@ -3041,7 +3473,12 @@ class WolfMapViewer(wx.Frame):
3041
3473
 
3042
3474
  return myimage
3043
3475
 
3044
- def save_canvasogl(self, fn:str='', mpl:bool=True, ds:float=0., dpi:int= 300):
3476
+ def save_canvasogl(self,
3477
+ fn:str='',
3478
+ mpl:bool=True,
3479
+ ds:float=0.,
3480
+ dpi:int= 300,
3481
+ add_title:bool = False):
3045
3482
  """
3046
3483
  Sauvegarde de la fenêtre d'affichage dans un fichier
3047
3484
 
@@ -3151,6 +3588,8 @@ class WolfMapViewer(wx.Frame):
3151
3588
 
3152
3589
  # self.Paint()
3153
3590
 
3591
+ if add_title:
3592
+ ax.set_title(self.viewer_name)
3154
3593
  fig.set_size_inches(12, 10)
3155
3594
  fig.tight_layout()
3156
3595
 
@@ -3253,23 +3692,37 @@ class WolfMapViewer(wx.Frame):
3253
3692
  self.mywmsfore = []
3254
3693
  self.myres2D = []
3255
3694
  self.myviewers3d = []
3695
+ self.sim_explorers = {}
3256
3696
 
3257
3697
  # liste des éléments modifiable dans l'arbre
3258
3698
  self.all_lists = [self.myarrays, self.myvectors, self.myclouds, self.mytri, self.myothers, self.myviews, self.myres2D, self.mytiles, self.mypartsystems, self.myviewers3d]
3259
3699
 
3700
+ self.menu_options = wx.Menu()
3701
+ self._change_title = self.menu_options.Append(wx.ID_ANY, _('Change title'), _('Change title of the window'))
3702
+ self.Bind(wx.EVT_MENU, self.OnChangeTitle, self._change_title)
3703
+
3260
3704
  if self.get_configuration() is not None:
3261
3705
  # see PyGui.py if necessary
3262
3706
 
3263
- self.menu_options = wx.Menu()
3264
3707
  self.menubar.Append(self.menu_options, _('Options'))
3265
3708
  self.option_global = self.menu_options.Append(wx.ID_ANY,_("Global"),_("Modify global options"))
3266
3709
  self.Bind(wx.EVT_MENU, self.GlobalOptionsDialog, self.option_global)
3267
3710
 
3268
- self.menu_1to9 =self.menu_options.Append(wx.ID_ANY, _('Colors for selections 1->9'), _('Selections'))
3269
- self.Bind(wx.EVT_MENU, self.colors1to9.change_colors, self.menu_1to9)
3711
+ self.menu_1to9 =self.menu_options.Append(wx.ID_ANY, _('Colors for selections 1->9'), _('Selections'))
3712
+ self.Bind(wx.EVT_MENU, self.colors1to9.change_colors, self.menu_1to9)
3270
3713
 
3271
3714
  self.Show(True)
3272
3715
 
3716
+ def OnChangeTitle(self, e):
3717
+ """
3718
+ Change the title of the window
3719
+ """
3720
+
3721
+ dlg = wx.TextEntryDialog(None, _('Enter the new title'), _('Change title'), self.GetTitle())
3722
+ if dlg.ShowModal() == wx.ID_OK:
3723
+ self.SetTitle(dlg.GetValue())
3724
+ dlg.Destroy()
3725
+
3273
3726
  def OnSize(self, e):
3274
3727
  """
3275
3728
  Redimensionnement de la fenêtre
@@ -4645,6 +5098,8 @@ class WolfMapViewer(wx.Frame):
4645
5098
  else:
4646
5099
  return
4647
5100
 
5101
+ logging.info(_('This could take some time for large area...\n Take a coffee and relax!'))
5102
+
4648
5103
  bounds = array.get_bounds()
4649
5104
 
4650
5105
  # align bounds on chunk_size
@@ -4738,7 +5193,7 @@ class WolfMapViewer(wx.Frame):
4738
5193
  array.reset_plot()
4739
5194
  self.Paint()
4740
5195
 
4741
- logging.info(_('Filling done'))
5196
+ logging.info(_('Filling done !'))
4742
5197
 
4743
5198
  def count_active_array_from_laz(self, array:WolfArray = None, used_codes:list = [], chunk_size:float = 500.):
4744
5199
  """ Fill active array with laz data
@@ -5005,6 +5460,7 @@ class WolfMapViewer(wx.Frame):
5005
5460
 
5006
5461
  curmodel.read_oneresult()
5007
5462
  curmodel.set_currentview()
5463
+ self._update_sim_explorer(curmodel)
5008
5464
 
5009
5465
  self.Refresh()
5010
5466
  self.currently_readresults = False
@@ -5023,6 +5479,8 @@ class WolfMapViewer(wx.Frame):
5023
5479
 
5024
5480
  curmodel.read_oneresult(which)
5025
5481
  curmodel.set_currentview()
5482
+ self._update_sim_explorer(curmodel)
5483
+
5026
5484
 
5027
5485
  self.Refresh()
5028
5486
  self.currently_readresults = False
@@ -5041,6 +5499,7 @@ class WolfMapViewer(wx.Frame):
5041
5499
 
5042
5500
  curmodel.read_previous()
5043
5501
  curmodel.set_currentview()
5502
+ self._update_sim_explorer(curmodel)
5044
5503
 
5045
5504
  self.Refresh()
5046
5505
  self.currently_readresults = False
@@ -5082,6 +5541,8 @@ class WolfMapViewer(wx.Frame):
5082
5541
 
5083
5542
  curmodel.read_next()
5084
5543
  curmodel.set_currentview()
5544
+ self._update_sim_explorer(curmodel)
5545
+
5085
5546
 
5086
5547
  self.Refresh()
5087
5548
  self.currently_readresults = False
@@ -5364,6 +5825,8 @@ class WolfMapViewer(wx.Frame):
5364
5825
  linkedarrays = self.get_linked_arrays()
5365
5826
 
5366
5827
  with wx.MultiChoiceDialog(None, _('Choose the arrays to plot'), _('Arrays'), [curarray for curarray in list(linkedarrays.keys())]) as dlg:
5828
+ dlg.SetSelections(range(len(linkedarrays)))
5829
+
5367
5830
  if dlg.ShowModal() == wx.ID_CANCEL:
5368
5831
  dlg.Destroy()
5369
5832
  return
@@ -6094,9 +6557,9 @@ class WolfMapViewer(wx.Frame):
6094
6557
  autoscale = False
6095
6558
 
6096
6559
  fn, ds = self.save_canvasogl(mpl=True)
6097
- all_images = self.save_linked_canvas(fn[:-4], mpl= True, ds= ds)
6098
6560
 
6099
- self.assembly_images(all_images)
6561
+ all_images = self.save_linked_canvas(fn[:-4], mpl= True, ds= ds, add_title= True)
6562
+ self.assembly_images(all_images, mode= self.assembly_mode)
6100
6563
 
6101
6564
  elif itemlabel == _('Copy image...'):
6102
6565
  autoscale = False
@@ -6405,31 +6868,40 @@ class WolfMapViewer(wx.Frame):
6405
6868
 
6406
6869
  logging.info(_('Filtering done !'))
6407
6870
 
6408
- def export_results_as(self, which:Literal['geotiff','shape'] = None, multiband:bool = None):
6871
+ def export_results_as(self, which:Literal['geotiff','shape','numpy'] = None, multiband:bool = None):
6409
6872
  """
6410
- Export des résultats WOLF2D vers le format GeoTiff
6411
- On attend que les matrices ".hbin" aient été chargées dans l'interface
6873
+ Export des résultats WOLF2D vers différents formats.
6874
+ Au moins un résultat doit être chargé pour pouvoir être exporté.
6412
6875
  """
6413
6876
 
6414
6877
  dlg = wx.DirDialog(self,_('Choose output directory'), style = wx.DD_DIR_MUST_EXIST)
6415
6878
  ret=dlg.ShowModal()
6416
6879
 
6417
6880
  if ret == wx.ID_CANCEL:
6881
+ logging.warning(_('Abort!'))
6418
6882
  dlg.Destroy()
6419
6883
  return
6420
6884
 
6421
6885
  outdir = dlg.GetPath()
6422
6886
  dlg.Destroy()
6423
6887
 
6424
- if which not in ['geotiff','shape']:
6425
- dlg = wx.SingleChoiceDialog(self,_('Choose output format'), _('Format'), ['Geotiff','Shape file'])
6426
- dlg.ShowModal()
6888
+ if which not in ['geotiff','shape','numpy']:
6889
+ dlg = wx.SingleChoiceDialog(self,_('Choose output format'), _('Format'), ['Geotiff','Shape file','Numpy array'])
6890
+ ret = dlg.ShowModal()
6891
+
6892
+ if ret == wx.ID_CANCEL:
6893
+ logging.warning(_('Abort!'))
6894
+ dlg.Destroy()
6895
+ return
6896
+
6427
6897
  sel = dlg.GetSelection()
6428
6898
 
6429
6899
  if sel == 0:
6430
6900
  which = 'geotiff'
6431
- else:
6901
+ elif sel == 1:
6432
6902
  which = 'shape'
6903
+ else:
6904
+ which = 'numpy'
6433
6905
 
6434
6906
  dlg.Destroy()
6435
6907
 
@@ -6452,7 +6924,12 @@ class WolfMapViewer(wx.Frame):
6452
6924
 
6453
6925
  dlg = wx.MultiChoiceDialog(self,_('Choose results to export'), _('Results'), choices=loaded_res)
6454
6926
  dlg.SetSelections([idx for idx, res in enumerate(loaded_res) if self.get_obj_from_id(res, drawtype=draw_type.RES2D).plotted])
6455
- dlg.ShowModal()
6927
+ ret = dlg.ShowModal()
6928
+ if ret == wx.ID_CANCEL:
6929
+ logging.warning(_('Abort!'))
6930
+ dlg.Destroy()
6931
+ return
6932
+
6456
6933
  sel = dlg.GetSelections() # Get a list if integers
6457
6934
  sel_res = [self.get_obj_from_id(loaded_res[cursel], drawtype=draw_type.RES2D) for cursel in sel] # convert to list of objects
6458
6935
 
@@ -6478,7 +6955,13 @@ class WolfMapViewer(wx.Frame):
6478
6955
 
6479
6956
  dlg = wx.MultiChoiceDialog(self,_('Choose fields to export'), _('Fields'), choices= [str(field[0]) for field in fields])
6480
6957
  dlg.SetSelections([idx for idx, field in enumerate(fields) if field[1]])
6481
- dlg.ShowModal()
6958
+ ret = dlg.ShowModal()
6959
+
6960
+ if ret == wx.ID_CANCEL:
6961
+ logging.warning(_('Abort!'))
6962
+ dlg.Destroy()
6963
+ return
6964
+
6482
6965
  sel_fields = dlg.GetSelections() # Get a list if integers
6483
6966
  dlg.Destroy()
6484
6967
 
@@ -6666,13 +7149,20 @@ class WolfMapViewer(wx.Frame):
6666
7149
 
6667
7150
  return linkedarrays
6668
7151
 
6669
- def save_linked_canvas(self, fn, mpl=True, ds=0.):
6670
- """ Save canvas of all linked viewers """
7152
+ def save_linked_canvas(self, fn:str, mpl:bool= True, ds:float= 0., add_title:bool= True) -> tuple[(str, float), str]:
7153
+ """ Save canvas of all linked viewers
7154
+
7155
+ :param fn: filename without extension -- '.png' will be added
7156
+ :param mpl: save as matplotlib image
7157
+ :param ds: Ticks size for matplotlib image
7158
+ :return: list of tuple ((filename, ds), viewer_name)
7159
+ """
6671
7160
 
7161
+ fn = str(fn)
6672
7162
  ret = []
6673
7163
  if self.linked:
6674
7164
  for idx, curel in enumerate(self.linkedList):
6675
- ret.append(curel.save_canvasogl(fn + '_' + str(idx) + '.png', mpl, ds))
7165
+ ret.append((curel.save_canvasogl(fn + '_' + str(idx) + '.png', mpl, ds, add_title= add_title), self.viewer_name))
6676
7166
 
6677
7167
  return ret
6678
7168
 
@@ -6680,50 +7170,55 @@ class WolfMapViewer(wx.Frame):
6680
7170
  """ Assembly images
6681
7171
 
6682
7172
  Every image has the same size (width, height)
7173
+
7174
+ :param all_images: list of tuple (filename, viewer_name)
7175
+ :param mode: 'horizontal', 'vertical', 'square'
6683
7176
  """
6684
7177
 
7178
+ assert mode in ['horizontal', 'vertical', 'square', 0, 1, 2], 'Mode not recognized'
7179
+
6685
7180
  from PIL import Image
6686
7181
 
6687
- images = [Image.open(fn) for fn, ds in all_images]
7182
+ images = [Image.open(fn) for (fn, ds), viewername in all_images]
6688
7183
 
6689
7184
  widths, heights = zip(*(i.size for i in images))
6690
7185
 
6691
- if mode == 'horizontal':
7186
+ if mode == 'horizontal' or mode==0:
6692
7187
 
6693
7188
  total_width = sum(widths)
6694
7189
  max_height = max(heights)
6695
7190
 
6696
- new_im = Image.new('RGB', (total_width, max_height))
7191
+ new_im = Image.new('RGB', (total_width, max_height), color=(255,255,255))
6697
7192
 
6698
7193
  x_offset = 0
6699
7194
  for im in images:
6700
7195
  new_im.paste(im, (x_offset,0))
6701
7196
  x_offset += im.size[0]
6702
7197
 
6703
- new_im.save(all_images[0][0][:-4] + '_assembly.png')
7198
+ new_im.save(all_images[0][0][0][:-4] + '_assembly.png')
6704
7199
 
6705
- elif mode == 'vertical':
7200
+ elif mode == 'vertical' or mode==1:
6706
7201
 
6707
7202
  total_height = sum(heights)
6708
7203
  max_width = max(widths)
6709
7204
 
6710
- new_im = Image.new('RGB', (max_width, total_height))
7205
+ new_im = Image.new('RGB', (max_width, total_height), color=(255,255,255))
6711
7206
 
6712
7207
  y_offset = 0
6713
7208
  for im in images:
6714
7209
  new_im.paste(im, (0, y_offset))
6715
7210
  y_offset += im.size[1]
6716
7211
 
6717
- new_im.save(all_images[0][0][:-4] + '_assembly.png')
7212
+ new_im.save(all_images[0][0][0][:-4] + '_assembly.png')
6718
7213
 
6719
- elif mode == 'square':
7214
+ elif mode == 'square' or mode==2:
6720
7215
 
6721
7216
  max_width = max(widths)
6722
7217
  max_height = max(heights)
6723
7218
 
6724
7219
  nb_hor = int(np.ceil(np.sqrt(len(images))))
6725
7220
 
6726
- new_im = Image.new('RGB', (max_width*nb_hor, max_height*nb_hor))
7221
+ new_im = Image.new('RGB', (max_width*nb_hor, max_height*nb_hor), color=(255,255,255))
6727
7222
 
6728
7223
  x_offset = 0
6729
7224
  y_offset = 0
@@ -6734,7 +7229,9 @@ class WolfMapViewer(wx.Frame):
6734
7229
  y_offset += im.size[1]
6735
7230
  x_offset = 0
6736
7231
 
6737
- new_im.save(all_images[0][0][:-4] + '_assembly.png')
7232
+ new_im.save(all_images[0][0][0][:-4] + '_assembly.png')
7233
+
7234
+ return new_im
6738
7235
 
6739
7236
  def thread_update_blender(self):
6740
7237
  print("Update blender")
@@ -9380,7 +9877,7 @@ class WolfMapViewer(wx.Frame):
9380
9877
  # \n \
9381
9878
  # CTRL+Q : Quit application\n \
9382
9879
  # CTRL+U : Import GLTF/GLB\n \
9383
- # CTRL+C : Set copy source / Copy canvas to Clipboard\n \
9880
+ # CTRL+C : Set copy source\n \
9384
9881
  # CTRL+V : Paste selected values\n \
9385
9882
  # CTRL+ALT+V ou ALTGr+V : Paste/Recopy selection\n \
9386
9883
  # CTRL+L : chargement d'une matrice sur base du nom de fichier de la tile\n \
@@ -9416,7 +9913,8 @@ class WolfMapViewer(wx.Frame):
9416
9913
  'F7': _('Drawing : refresh'),
9417
9914
  'Arrows': _('Drawing : lateral movements'),
9418
9915
  'c or C': _('Drawing : copy canvas to Clipboard wo axes'),
9419
- 'CTRL+C': _('Drawing : copy canvas to Clipboard as Matplotlib image'),
9916
+ 'ALT+C': _('Drawing : copy canvas to Clipboard as Matplotlib image'),
9917
+ 'ALT+SHIFT+C': _('Drawing : copy canvas to Clipboard as Matplotlib image with axes - linked arrays'),
9420
9918
 
9421
9919
  'CTRL+o': _('Results : increase transparency of the current result'),
9422
9920
  'CTRL+O': _('Results : decrease transparency of the current result'),
@@ -9542,7 +10040,25 @@ class WolfMapViewer(wx.Frame):
9542
10040
  logging.debug(_('Alt is down'))
9543
10041
 
9544
10042
  if ctrldown or altdown:
9545
- if key == wx.WXK_F2 and not shiftdown:
10043
+ if key == 60 and shiftdown: #'>'
10044
+ if self.active_array is not None:
10045
+ if self.active_array.SelectionData is not None:
10046
+ self.active_array.SelectionData.dilate_contour_selection(1)
10047
+ self.active_array.reset_plot()
10048
+ elif key == 60 and not shiftdown: #'<'
10049
+ if self.active_array is not None:
10050
+ if self.active_array.SelectionData is not None:
10051
+ self.active_array.SelectionData.erode_contour_selection()
10052
+ self.active_array.reset_plot()
10053
+
10054
+ elif key == wx.WXK_F2 and ctrldown and altdown and shiftdown:
10055
+
10056
+ if self.active_res2d is None:
10057
+ logging.info(_('Please activate a simulation before search a specific result'))
10058
+
10059
+ self._add_sim_explorer(self.active_res2d)
10060
+
10061
+ elif key == wx.WXK_F2 and not shiftdown:
9546
10062
 
9547
10063
  if self.active_res2d is not None:
9548
10064
  nb = self.active_res2d.get_nbresults()
@@ -9556,20 +10072,11 @@ class WolfMapViewer(wx.Frame):
9556
10072
  self.active_res2d.set_currentview()
9557
10073
  self.Refresh()
9558
10074
 
10075
+ self._update_sim_explorer()
10076
+
9559
10077
  else:
9560
10078
  logging.info(_('Please activate a simulation before search a specific result'))
9561
10079
 
9562
- elif key == 60 and shiftdown: #'>'
9563
- if self.active_array is not None:
9564
- if self.active_array.SelectionData is not None:
9565
- self.active_array.SelectionData.dilate_contour_selection(1)
9566
- self.active_array.reset_plot()
9567
- elif key == 60 and not shiftdown: #'<'
9568
- if self.active_array is not None:
9569
- if self.active_array.SelectionData is not None:
9570
- self.active_array.SelectionData.erode_contour_selection()
9571
- self.active_array.reset_plot()
9572
-
9573
10080
  elif key == wx.WXK_F2 and shiftdown:
9574
10081
 
9575
10082
  if self.active_res2d is not None:
@@ -9595,6 +10102,8 @@ class WolfMapViewer(wx.Frame):
9595
10102
  self.active_res2d.set_currentview()
9596
10103
  self.Refresh()
9597
10104
 
10105
+ self._update_sim_explorer()
10106
+
9598
10107
  else:
9599
10108
  logging.info(_('Please activate a simulation before searching a specific result'))
9600
10109
 
@@ -9612,6 +10121,7 @@ class WolfMapViewer(wx.Frame):
9612
10121
  self.active_particle_system.current_step = nb-1
9613
10122
  self.Refresh()
9614
10123
  self._update_mytooltip()
10124
+ self._update_sim_explorer()
9615
10125
 
9616
10126
  else:
9617
10127
  logging.info(_('Please activate a particle system before searching a specific result'))
@@ -9639,6 +10149,7 @@ class WolfMapViewer(wx.Frame):
9639
10149
  self.active_particle_system.current_step = choices.index(keyvalue)
9640
10150
  self.Refresh()
9641
10151
  self._update_mytooltip()
10152
+ self._update_sim_explorer()
9642
10153
 
9643
10154
  else:
9644
10155
  logging.info(_('Please activate a simulation before search a specific result'))
@@ -9707,11 +10218,45 @@ class WolfMapViewer(wx.Frame):
9707
10218
  elif key == wx.WXK_DOWN:
9708
10219
  self.downobj()
9709
10220
 
9710
- elif key == ord('C') and altdown and not ctrldown:
10221
+ elif key == ord('C') and altdown and not ctrldown and not shiftdown:
9711
10222
  # ALT+C
9712
10223
  #Copie du canvas dans le clipboard pour transfert vers autre application
9713
10224
  self.copy_canvasogl()
9714
10225
 
10226
+ elif key == ord('C') and altdown and not ctrldown and shiftdown:
10227
+ # ALT+SHIFT+C
10228
+ # Copie du canvas dans le clipboard pour transfert vers autre application
10229
+ # Copie des canvas liés
10230
+
10231
+ from tempfile import TemporaryDirectory
10232
+
10233
+ logging.info(_('Creating images'))
10234
+
10235
+ with TemporaryDirectory() as tmpdirname:
10236
+ all_images = self.save_linked_canvas(Path(tmpdirname) / 'fig', mpl= True, ds= self.ticks_size, add_title= True)
10237
+ im_assembly = self.assembly_images(all_images, mode= self.assembly_mode)
10238
+
10239
+ logging.info(_('Creating images - done'))
10240
+
10241
+ # Copy image to clipboard
10242
+ if im_assembly is not None:
10243
+ if wx.TheClipboard.Open():
10244
+
10245
+ #création d'un objet bitmap wx
10246
+ wxbitmap = wx.Bitmap().FromBuffer(im_assembly.width, im_assembly.height, im_assembly.tobytes())
10247
+
10248
+ # objet wx exportable via le clipboard
10249
+ dataobj = wx.BitmapDataObject()
10250
+ dataobj.SetBitmap(wxbitmap)
10251
+
10252
+ wx.TheClipboard.SetData(dataobj)
10253
+ wx.TheClipboard.Close()
10254
+ logging.info(_('Image copied to clipboard'))
10255
+ else:
10256
+ logging.error(_('Cannot open the clipboard'))
10257
+ else:
10258
+ logging.error(_('No image to copy to clipboard'))
10259
+
9715
10260
  elif key == ord('C') and ctrldown and not altdown:
9716
10261
  # CTRL+C
9717
10262
  if self.active_array is None:
@@ -9975,7 +10520,13 @@ class WolfMapViewer(wx.Frame):
9975
10520
  self.active_array.myops.select_node_by_node()
9976
10521
 
9977
10522
  if self.active_res2d is not None:
9978
- self.active_res2d.properties.select_node_by_node()
10523
+ if self.active_array is not None:
10524
+ msg = wx.MessageDialog(None, _('Do you want to select the nodes of the active result ?'), _('Select nodes'), wx.YES_NO | wx.ICON_QUESTION)
10525
+ ret = msg.ShowModal()
10526
+ if ret == wx.ID_YES:
10527
+ self.active_res2d.properties.select_node_by_node()
10528
+ else:
10529
+ self.active_res2d.properties.select_node_by_node()
9979
10530
 
9980
10531
  if self.active_array is None and self.active_res2d is None:
9981
10532
  logging.warning(_('No active array or result 2D to select node by node !'))