wolfhece 2.1.7__py3-none-any.whl → 2.1.9__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/PyParams.py CHANGED
@@ -944,7 +944,11 @@ class Wolf_Param(wx.Frame):
944
944
  except:
945
945
  logging.debug("String type will be conserved! -- {}".format(value_param))
946
946
 
947
- page.AppendIn(parent, pg.EnumProperty(param_name, name=locname, labels=list_keys, values=list_values, value=value_param))
947
+ if type(value_param) != int:
948
+ logging.warning("Parameters -- EnumProperty -- Value {} is not an integer".format(value_param))
949
+ logging.debug("EnumProperty value must be an integer")
950
+
951
+ page.AppendIn(parent, pg.EnumProperty(param_name, name=locname, labels=list_keys, values=list_values, value=int(value_param))) # force value to 'int' type
948
952
  else:
949
953
  self._insert_with_type_based_on_value(page, group, param, prefix)
950
954
 
@@ -1055,8 +1059,12 @@ class Wolf_Param(wx.Frame):
1055
1059
  value_param = int(value_param)
1056
1060
  except:
1057
1061
  logging.debug("String type will be conserved! -- {}".format(value_param))
1058
- # print(value_param)
1059
- page.Append(pg.EnumProperty(param_name, name=locname, labels=list_keys, values=list_values, value=value_param))
1062
+
1063
+ if type(value_param) != int:
1064
+ logging.warning("Parameters -- EnumProperty -- Value {} is not an integer".format(value_param))
1065
+ logging.debug("EnumProperty value must be an integer")
1066
+
1067
+ page.Append(pg.EnumProperty(label= param_name, name= locname, labels= list_keys, values= list_values, value= int(value_param)))
1060
1068
 
1061
1069
  else:
1062
1070
  # Pas de chaîne JSON mais un commentaire complet
wolfhece/apps/version.py CHANGED
@@ -5,7 +5,7 @@ class WolfVersion():
5
5
 
6
6
  self.major = 2
7
7
  self.minor = 1
8
- self.patch = 7
8
+ self.patch = 9
9
9
 
10
10
  def __str__(self):
11
11
 
wolfhece/multiprojects.py CHANGED
@@ -80,7 +80,103 @@ class Wolf2D_Project(Project):
80
80
  self.poly = None
81
81
  self.poly_values = None
82
82
 
83
- def load_simulations(self, epsilon, verbose=False):
83
+ @classmethod
84
+ def from_existing(cls, sims:dict[str, Union[str, Wolfresults_2D, wolfres2DGPU]]):
85
+ """
86
+ Create a MultiProjects object from a dictionary of simulations
87
+
88
+ :param sims: dict[str, Union[str, Wolfresults_2D, wolfres2DGPU]] -- dictionary of simulations to add
89
+ """
90
+
91
+ newmp = cls(to_read=False)
92
+ newmp.add_simulations(sims)
93
+
94
+ return newmp
95
+
96
+ def __str__(self) -> str:
97
+ """
98
+ Return string representation
99
+ """
100
+
101
+ ret = f'Project : {Path(self.wdir)}\n'
102
+ ret += 'Key : Simulations : Type\n'
103
+ for curkey, cursim in self.mysims.items():
104
+ ret+=f'{curkey} : {cursim.filename} : {"GPU" if isinstance(cursim, wolfres2DGPU) else "CPU"}\n'
105
+
106
+ return ret
107
+
108
+ def __getitem__(self, key: tuple[str, str]):
109
+
110
+ return self.get_simulation(key)
111
+
112
+ def pop(self, key: str):
113
+ """
114
+ Remove a simulation from the project
115
+
116
+ :param key: str -- key of the simulation to remove
117
+ """
118
+
119
+ if key in self.mysims.keys():
120
+ self.mysims.pop(key)
121
+
122
+ def add(self,
123
+ key:str,
124
+ value:Union[Wolfresults_2D, wolfres2DGPU, str],
125
+ epsilon:float=None,
126
+ force_read:bool = False):
127
+ """
128
+ Add a key-value pair to the project
129
+
130
+ :param key: str -- key of the simulation to add
131
+ :param value: Wolfresults_2D, wolfres2DGPU, str -- simulation to add
132
+ :param epsilon: float -- epsilon value to use for the simulation
133
+ :param force_read: bool -- force reading the last step of the simulation
134
+
135
+ """
136
+
137
+ if epsilon is None:
138
+ epsilon = self.epsilon
139
+
140
+ if isinstance(value, str):
141
+
142
+ locpath = Path(self.wdir) / value
143
+ if locpath.exists():
144
+
145
+ if 'simul_gpu_results' in locpath.name or (locpath / 'simul_gpu_results').exists():
146
+ #GPU
147
+ self.mysims[key] = wolfres2DGPU(locpath, epsilon=self.epsilon, idx=key)
148
+ else:
149
+ #CPU
150
+ self.mysims[key] = Wolfresults_2D(locpath, epsilon=self.epsilon, idx=key)
151
+
152
+ else:
153
+ logging.warning(f'File {locpath} does not exist !')
154
+
155
+ elif isinstance(value, wolfres2DGPU):
156
+
157
+ self.mysims[key] = value
158
+
159
+ elif isinstance(value, Wolfresults_2D):
160
+
161
+ self.mysims[key] = value
162
+
163
+ if force_read:
164
+ self.mysims[key].read_oneresult()
165
+
166
+ def add_simulations(self,
167
+ sims_to_add:dict[str, Union[str, Wolfresults_2D, wolfres2DGPU]],
168
+ epsilon:float=None,
169
+ force_read:bool = False):
170
+ """
171
+ Add multiple simulations to the project
172
+
173
+ :param sims_to_add: dict[str, Union[str, Wolfresults_2D, wolfres2DGPU]] -- dictionary of simulations to add
174
+ """
175
+
176
+ for key, value in sims_to_add.items():
177
+ self.add(key, value, epsilon, force_read)
178
+
179
+ def load_simulations(self, epsilon:float, verbose:bool=False):
84
180
  """
85
181
  Load all simulations in current project
86
182
  """
@@ -92,6 +188,7 @@ class Wolf2D_Project(Project):
92
188
  for key,val in sims_wolf2d.items():
93
189
  if verbose:
94
190
  print(key)
191
+ logging.info(f'Loading simulation {key} : {val[key_Param.VALUE]}')
95
192
 
96
193
  cursim = self.mysims[key] = Wolfresults_2D(join(self.wdir, val[key_Param.VALUE]), eps=epsilon, idx=key)
97
194
  cursim.plotted=True
@@ -102,6 +199,7 @@ class Wolf2D_Project(Project):
102
199
  for key,val in sims_gpu2d.items():
103
200
  if verbose:
104
201
  print(key)
202
+ logging.info(f'Loading simulation {key} : {val[key_Param.VALUE]}')
105
203
 
106
204
  cursim = self.mysims[key] = wolfres2DGPU(Path(join(self.wdir, val[key_Param.VALUE])), eps=epsilon, idx=key)
107
205
  cursim.plotted=True
@@ -128,6 +226,23 @@ class Wolf2D_Project(Project):
128
226
  for cursim in self.mysims.values():
129
227
  cursim.read_oneresult()
130
228
 
229
+ def get_simulation(self, key:Union[str,int]) -> Union[Wolfresults_2D, wolfres2DGPU]:
230
+ """
231
+ Get simulation by key
232
+
233
+ :param key: str or int -- key of the simulation to get or index of the simulation to get
234
+ """
235
+
236
+ if isinstance(key,int):
237
+ if key >=0 and key <len(self.mysims):
238
+ return self.mysims[self.mysims.keys()[key]]
239
+
240
+ elif isinstance(key,str):
241
+ if key in self.mysims.keys():
242
+ return self.mysims[key]
243
+
244
+ return None
245
+
131
246
  def get_simulations(self) -> list:
132
247
  """
133
248
  Return a python list of simulations
@@ -274,15 +389,58 @@ class Wolf2D_Project(Project):
274
389
 
275
390
  class MultiProjects():
276
391
  """Manager of multiple project files"""
392
+
277
393
  def __init__(self, wdir='') -> None:
278
394
 
279
395
  self.projects:dict[str, Wolf2D_Project]={}
280
396
  self.wdir=wdir
281
397
 
398
+ def __getitem__(self, key:Union[str,int]) -> Project:
399
+ """
400
+ Get project by key
401
+ """
402
+
403
+ return self.get_project(key)
404
+
405
+ def get_one_simulation(self, key_project:str, key_sim:str) -> Union[Wolfresults_2D, wolfres2DGPU]:
406
+ """
407
+ Get one simulation by key in a project
408
+ """
409
+
410
+ if key_project in self.projects.keys():
411
+ return self.projects[key_project].get_simulation(key_sim)
412
+
413
+ return None
414
+
415
+ def get_same_simulation_in_projects(self, key_sim:str) -> dict[str, Union[Wolfresults_2D, wolfres2DGPU]]:
416
+ """
417
+ Get simulation by key in all projects
418
+ """
419
+
420
+ allsims = {}
421
+ for curkey, curproj in self.projects.items():
422
+ cursim = curproj.get_simulation(key_sim)
423
+ if cursim is not None:
424
+ allsims[curkey + ' - ' + key_sim] = cursim
425
+
426
+ return allsims
427
+
428
+ def __str__(self) -> str:
429
+ """
430
+ Return string representation
431
+ """
432
+
433
+ ret = 'Key : Directory\n'
434
+ for curkey,curproj in self.projects.items():
435
+ ret+=f'{curkey} : {Path(curproj.wdir)}\n'
436
+
437
+ return ret
438
+
282
439
  def add(self, project:Project, key=str, whichtype=project_type.GENERIC):
283
440
  """
284
441
  Add project to dict
285
442
  """
443
+
286
444
  if isinstance(project,str):
287
445
  if exists(project):
288
446
  pass
@@ -300,6 +458,7 @@ class MultiProjects():
300
458
  """
301
459
  Récupération d'un projet sur base du nom ou d'une position
302
460
  """
461
+
303
462
  if isinstance(key,int):
304
463
  if key >=0 and key <len(self.projects):
305
464
  return self.projects[self.projects.keys()[key]]
@@ -309,11 +468,12 @@ class MultiProjects():
309
468
  return self.projects[key]
310
469
 
311
470
  return None
312
-
471
+
313
472
  def read(self, filepath:str):
314
473
  """
315
474
  Read from file
316
475
  """
476
+
317
477
  if exists(filepath):
318
478
  with open(filepath,'r') as f:
319
479
  self.projects = json.load(f)
@@ -322,6 +482,7 @@ class MultiProjects():
322
482
  """
323
483
  Write to file
324
484
  """
485
+
325
486
  with open(filepath,'w') as f:
326
487
  json.dump(self.projects,f)
327
488
 
@@ -329,6 +490,7 @@ class MultiProjects():
329
490
  """
330
491
  Load all simulations in projects
331
492
  """
493
+
332
494
  for keyp,valp in self.projects.items():
333
495
  print(keyp)
334
496
  valp.load_simulations(epsilon, verbose)
@@ -337,11 +499,12 @@ class MultiProjects():
337
499
  """
338
500
  Update all simulations in projects
339
501
  """
502
+
340
503
  for keyp,valp in self.projects.items():
341
504
  print(keyp)
342
505
  valp.update_simulations(epsilon, verbose)
343
506
 
344
- def get_simulations_list(self, which_project=None) -> list:
507
+ def get_simulations_list(self, which_project:Union[str, list[str]]=None) -> list:
345
508
  """
346
509
  Return a python list of simulations
347
510
 
wolfhece/pybridges.py CHANGED
@@ -31,6 +31,7 @@ class stored_values_unk(Enum):
31
31
  UNORM = (5, views_2D.UNORM.value)
32
32
  FROUDE = (6, views_2D.FROUDE.value)
33
33
  WATERLEVEL = (7, views_2D.WATERLEVEL.value)
34
+ WATERSTAGE = (7, views_2D.WATERLEVEL.value)
34
35
  TOPOGRAPHY = (8, views_2D.TOPOGRAPHY.value)
35
36
  HEAD = (-1, _('Difference of waterlevel (up-down)'))
36
37
  DIFFERENCE_Z_UP_DOWN = (-1, _('Difference of waterlevel (up-down)'))
@@ -7,20 +7,151 @@ from typing import Literal, Union
7
7
  import matplotlib.pyplot as plt
8
8
  from enum import Enum
9
9
  import logging
10
+ from pathlib import Path
10
11
 
11
12
  from .PyTranslate import _
12
13
  from .PyVertexvectors import Zones, zone, vector, vectorproperties, getIfromRGB
13
14
  from .drawing_obj import Element_To_Draw
14
15
  from .PyTranslate import _
15
- from .wolfresults_2D import views_2D
16
+ from .wolfresults_2D import views_2D, Wolfresults_2D
17
+ from .Results2DGPU import wolfres2DGPU
16
18
  from .pybridges import stored_values_pos,stored_values_unk, parts_values, operators, stored_values_coords
17
19
 
20
+ from zipfile import ZIP_DEFLATED, ZipFile
21
+
22
+ class ZipFileWrapper(ZipFile):
23
+ def open(self, name="data", mode="r", pwd=None, **kwargs):
24
+ return super().open(name=name, mode=mode, pwd=pwd, **kwargs)
25
+
26
+ def read(self):
27
+ return super().read(name="data")
28
+
18
29
  class Extracting_Zones(Zones):
30
+ """
31
+ Classe permettant de récupérer les valeurs à l'intérieur des polygones
32
+ définis dans plusieurs zones.
33
+
34
+ Ces polygones ne sont pas nécessairement ordonnés ou relatifs au lit mineur.
35
+
36
+ """
19
37
 
20
38
  def __init__(self, filename='', ox: float = 0, oy: float = 0, tx: float = 0, ty: float = 0, parent=None, is2D=True, idx: str = '', plotted: bool = True, mapviewer=None, need_for_wx: bool = False) -> None:
21
39
  super().__init__(filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx)
22
- self.parts = {}
23
- self.linked = {}
40
+
41
+ self.parts:dict = None
42
+ self.linked:Union[dict, list] = None
43
+
44
+ def cache_data(self, outputfile:str):
45
+ """
46
+ Serialize the values in a file
47
+ """
48
+ self._serialize_values(outputfile)
49
+
50
+ def load_data(self, inputfile:str):
51
+ """
52
+ Deserialize the values from a file
53
+ """
54
+ self._deserialize_values(inputfile)
55
+
56
+ def _serialize_values(self, outputfile:str):
57
+ """
58
+ Serialize the values in a file
59
+ """
60
+
61
+ import json
62
+ from codecs import getwriter
63
+ from typing import IO
64
+
65
+ class NumpyArrayEncoder(json.JSONEncoder):
66
+ def default(self, obj):
67
+ if isinstance(obj, np.integer):
68
+ return int(obj)
69
+ elif isinstance(obj, np.floating):
70
+ return float(obj)
71
+ elif isinstance(obj, np.ndarray):
72
+ return obj.tolist()
73
+ elif obj == Wolfresults_2D:
74
+ return 'CPU'
75
+ elif obj == wolfres2DGPU:
76
+ return 'GPU'
77
+
78
+ return json.JSONEncoder.default(self, obj)
79
+
80
+ def _json_dump_bytes(fp: IO, obj):
81
+ StreamWriter = getwriter("utf-8")
82
+ return json.dump(fp=StreamWriter(fp), obj=obj, cls=NumpyArrayEncoder, indent=4)
83
+
84
+ def json_dump_zip(fp: IO, obj):
85
+ with ZipFileWrapper(fp, mode="w", compression=ZIP_DEFLATED, compresslevel=9) as zip_file:
86
+ with zip_file.open(mode="w") as _fp:
87
+ _json_dump_bytes(fp=_fp, obj=obj)
88
+
89
+
90
+ with open(outputfile, 'wb') as f:
91
+
92
+ json_dump_zip(fp = f, obj = {'linked' : self.linked, 'values': self.parts})
93
+
94
+
95
+ def _deserialize_values(self, inputfile:str):
96
+ """
97
+ Deserialize the values from a file
98
+ """
99
+
100
+ import json
101
+ from codecs import getwriter
102
+ from typing import IO
103
+
104
+ def json_load_zip(fp: IO):
105
+ with ZipFileWrapper(fp, mode="r") as zip_file:
106
+ return json.load(zip_file)
107
+
108
+ inputfile = Path(inputfile)
109
+ if not inputfile.exists():
110
+ logging.error(_('File {0} does not exist').format(inputfile))
111
+ return
112
+
113
+ with open(inputfile, 'rb') as f:
114
+ data = json_load_zip(f) #json.load(f)
115
+
116
+ tmp_linked = data['linked']
117
+ if isinstance(tmp_linked, dict):
118
+ self.linked = {}
119
+ for curkey, curgroup in tmp_linked.items():
120
+ self.linked[curkey] = [(curlink[0], Wolfresults_2D if curlink[1] == 'CPU' else wolfres2DGPU) for curlink in curgroup]
121
+
122
+ tmp_values = data['values']
123
+ self.parts = {}
124
+
125
+ for cuzone, curparts in tmp_values.items():
126
+ self.parts[cuzone] = {}
127
+
128
+ for curproj, curdict in curparts.items():
129
+ self.parts[cuzone][curproj] = {}
130
+ for curpoly, curval in curdict.items():
131
+ self.parts[cuzone][curproj][curpoly] = {}
132
+ for curgroup, curarray in curval.items():
133
+ locdict = self.parts[cuzone][curproj][curpoly][curgroup] = {}
134
+
135
+ for cursim, curnparray in curarray.items():
136
+ locdict[cursim] = np.array([np.array([ tuple(lst1), np.array(lst2, dtype= np.int32)], dtype=object ) for lst1, lst2 in curnparray], dtype=object)
137
+
138
+ elif isinstance(tmp_linked, list):
139
+ self.linked = [(curlink[0], Wolfresults_2D if curlink[1] == 'CPU' else wolfres2DGPU) for curlink in tmp_linked]
140
+
141
+ tmp_values = data['values']
142
+ self.parts = {}
143
+
144
+ for cuzone, curparts in tmp_values.items():
145
+ self.parts[cuzone] = {}
146
+
147
+ for curpoly, curval in curparts.items():
148
+ self.parts[cuzone][curpoly] = {}
149
+ for curgroup, curarray in curval.items():
150
+ locdict = self.parts[cuzone][curpoly][curgroup] = {}
151
+
152
+ for cursim, curnparray in curarray.items():
153
+ locdict[cursim] = np.array([np.array([ tuple(lst1), np.array(lst2, dtype= np.int32)], dtype=object ) for lst1, lst2 in curnparray], dtype=object)
154
+
24
155
 
25
156
  def find_values_inside_parts(self, linked_arrays):
26
157
  """
@@ -45,13 +176,14 @@ class Extracting_Zones(Zones):
45
176
 
46
177
  self.linked = [(curlink.idx, type(curlink)) for curlink in linked_arrays]
47
178
 
179
+ self.parts = {}
48
180
  for curzone in self.myzones:
49
-
50
181
  if isinstance(linked_arrays, dict):
51
182
  locparts = self.parts[curzone.myname] = {}
52
183
  for curkey, curgroup in linked_arrays.items():
53
184
  locparts[curkey] = curzone.get_all_values_linked_polygon(curgroup, key_idx_names='name', getxy=True)
54
- elif isinstance(linked_arrays, list):
185
+
186
+ elif isinstance(linked_arrays, list):
55
187
  self.parts[curzone.myname] = curzone.get_all_values_linked_polygon(linked_arrays, key_idx_names='name', getxy=True)
56
188
 
57
189
  def _get_heads(self,
@@ -228,6 +360,14 @@ class Extracting_Zones(Zones):
228
360
 
229
361
  class Polygons_Analyze(Zones):
230
362
  """
363
+ Classe permettant de récupérer les valeurs à l'intérieur des polygones
364
+ définis dans la dernière zone d'une fichier .vecz.
365
+
366
+ Ce fichier est typiquement le résultat de la création de polygones
367
+ sur base de parallèles via l'interface graphique.
368
+
369
+ Utile notamment dans l'analyse de modélisations 2D (CPU et/ou GPU).
370
+
231
371
  """
232
372
 
233
373
  def __init__(self, myfile='', ds:float=5., ox: float = 0, oy: float = 0, tx: float = 0, ty: float = 0, parent=None, is2D=True, wx_exists:bool = False):
@@ -235,13 +375,15 @@ class Polygons_Analyze(Zones):
235
375
 
236
376
  self.myname = splitext(basename(myfile))[0]
237
377
 
238
- self.linked = {}
378
+ self.linked:Union[dict,list] = None # type is depending on the type of linked arrays
379
+ self.river_values:dict = None
239
380
 
240
381
  self.riverbed = self.get_zone(0).myvectors[1]
241
382
  self.riverbed.prepare_shapely()
242
383
 
243
384
  self.polygons_zone:zone
244
385
  self.polygons_zone = self.get_zone(-1)
386
+
245
387
  self.polygons_curvi = {}
246
388
  for curvert in self.polygons_zone.myvectors:
247
389
  self.polygons_curvi[curvert.myname] = curvert.myvertices[0].z
@@ -249,41 +391,130 @@ class Polygons_Analyze(Zones):
249
391
  for vec in self.polygons_zone.myvectors:
250
392
  vec.myprop.used=False # cache les polygones pour ne pas surcharger l'affichage éventuel
251
393
 
252
- # def colorize(self):
253
- # """Colorisation des polygones pour l'interface graphique"""
254
- # self.centralpart.myprop.color = getIfromRGB((0,255,0))
255
- # self.upstream.myprop.color = getIfromRGB((255,0,0))
256
- # self.downstream.myprop.color = getIfromRGB((0,0,255))
394
+ def cache_data(self, outputfile:str):
395
+ """
396
+ Serialize the values in a json file -- zipped
397
+ """
398
+
399
+ self._serialize_values(outputfile)
400
+
401
+ def load_data(self, inputfile:str):
402
+ """
403
+ Deserialize the values from a json file -- zipped
404
+ """
405
+
406
+ self._deserialize_values(inputfile)
257
407
 
258
- # def highlighting(self, rgb=(255,0,0), linewidth=3):
259
- # """
260
- # Mise en évidence
261
- # """
262
- # self.centralpart.highlighting(rgb,linewidth)
408
+ def _serialize_values(self, outputfile:str):
409
+ """
410
+ Serialize the values in a file
411
+ """
412
+
413
+ import json
414
+ from codecs import getwriter
415
+ from typing import IO
416
+
417
+ class NumpyArrayEncoder(json.JSONEncoder):
418
+ def default(self, obj):
419
+ if isinstance(obj, np.integer):
420
+ return int(obj)
421
+ elif isinstance(obj, np.floating):
422
+ return float(obj)
423
+ elif isinstance(obj, np.ndarray):
424
+ return obj.tolist()
425
+ elif obj == Wolfresults_2D:
426
+ return 'CPU'
427
+ elif obj == wolfres2DGPU:
428
+ return 'GPU'
429
+
430
+ return json.JSONEncoder.default(self, obj)
431
+
432
+ def _json_dump_bytes(fp: IO, obj):
433
+ StreamWriter = getwriter("utf-8")
434
+ return json.dump(fp=StreamWriter(fp), obj=obj, cls=NumpyArrayEncoder, indent=4)
435
+
436
+ def json_dump_zip(fp: IO, obj):
437
+ with ZipFileWrapper(fp, mode="w", compression=ZIP_DEFLATED, compresslevel=9) as zip_file:
438
+ with zip_file.open(mode="w") as _fp:
439
+ _json_dump_bytes(fp=_fp, obj=obj)
440
+
441
+
442
+ with open(outputfile, 'wb') as f:
443
+
444
+ json_dump_zip(fp = f, obj = {'linked' : self.linked, 'values': self.river_values})
445
+
446
+
447
+ def _deserialize_values(self, inputfile:str):
448
+ """
449
+ Deserialize the values from a file
450
+ """
451
+
452
+ import json
453
+ from codecs import getwriter
454
+ from typing import IO
455
+
456
+ def json_load_zip(fp: IO):
457
+ with ZipFileWrapper(fp, mode="r") as zip_file:
458
+ return json.load(zip_file)
459
+
460
+ inputfile = Path(inputfile)
461
+ if not inputfile.exists():
462
+ logging.error(_('File {0} does not exist').format(inputfile))
463
+ return
464
+
465
+ with open(inputfile, 'rb') as f:
466
+ data = json_load_zip(f) #json.load(f)
467
+
468
+ tmp_linked = data['linked']
469
+ if isinstance(tmp_linked, dict):
470
+ self.linked = {}
471
+ for curkey, curgroup in tmp_linked.items():
472
+ self.linked[curkey] = [(curlink[0], Wolfresults_2D if curlink[1] == 'CPU' else wolfres2DGPU) for curlink in curgroup]
473
+
474
+ tmp_values = data['values']
475
+ self.river_values = {}
476
+ for curproj, curdict in tmp_values.items():
477
+ self.river_values[curproj] = {}
478
+ for curpoly, curval in curdict.items():
479
+ self.river_values[curproj][curpoly] = {}
480
+ for curgroup, curarray in curval.items():
481
+ locdict = self.river_values[curproj][curpoly][curgroup] = {}
482
+
483
+ for cursim, curnparray in curarray.items():
484
+ locdict[cursim] = np.array([np.array([ tuple(lst1), np.array(lst2, dtype= np.int32)], dtype=object ) for lst1, lst2 in curnparray], dtype=object)
485
+
486
+ elif isinstance(tmp_linked, list):
487
+ self.linked = [(curlink[0], Wolfresults_2D if curlink[1] == 'CPU' else wolfres2DGPU) for curlink in tmp_linked]
488
+
489
+ tmp_values = data['values']
490
+ self.river_values = {}
491
+ for curpoly, curval in tmp_values.items():
492
+ self.river_values[curpoly] = {}
493
+ for curgroup, curarray in curval.items():
494
+ locdict = self.river_values[curpoly][curgroup] = {}
495
+
496
+ for cursim, curnparray in curarray.items():
497
+ locdict[cursim] = np.array([np.array([ tuple(lst1), np.array(lst2, dtype= np.int32)], dtype=object ) for lst1, lst2 in curnparray], dtype=object)
263
498
 
264
- # def withdrawal(self):
265
- # """
266
- # Mise en retrait
267
- # """
268
- # self.centralpart.withdrawal()
269
499
 
270
500
  def compute_distance(self, poly:LineString):
271
501
  """
272
502
  Compute the curvilinear distance along a support polyline
273
503
  """
504
+
274
505
  for curvert in self.polygons_zone.myvectors:
506
+ # Centre du polygone
275
507
  centerx = np.sum(np.asarray([cur.x for cur in curvert.myvertices[:4]]))/4.
276
508
  centery = np.sum(np.asarray([cur.y for cur in curvert.myvertices[:4]]))/4.
509
+
277
510
  self.polygons_curvi[curvert.myname] = poly.project(Point([centerx,centery]))
278
511
 
279
- def find_values_inside_parts(self, linked_arrays, force_reset = False):
512
+ def find_values_inside_parts(self, linked_arrays:Union[dict,list]):
280
513
  """
281
- Récupère les valeurs à l'intérieur :
282
- - des parties du pont (amont, centrale, aval)
283
- - de la discrétisation rivière en polygones
514
+ Récupère les valeurs à l'intérieur des polygones - dernière zone du fichier
284
515
 
285
- Retour :
286
- - dictionnaire dont la clé est le nom (ou l'index) du polygone dans la zone --> parties centrale, amont ou aval
516
+ Stockage :
517
+ - dictionnaire dont la clé est le nom (ou l'index) du polygone dans la zone
287
518
  - chaque entrée est un dictionnaire dont la clé 'values' contient un dictionnaire pour chaque matrice du projet
288
519
  - chaque élément de ce sous-dictionnaire est un tuple contenant toutes les valeurs utiles
289
520
 
@@ -292,11 +523,17 @@ class Polygons_Analyze(Zones):
292
523
  ***
293
524
 
294
525
  """
295
- if force_reset:
526
+
527
+ if isinstance(linked_arrays, dict):
528
+
296
529
  self.linked={}
297
530
 
298
- for curkey, curgroup in linked_arrays.items():
299
- self.linked[curkey] = [(curlink.idx, type(curlink)) for curlink in curgroup]
531
+ for curkey, curgroup in linked_arrays.items():
532
+ self.linked[curkey] = [(curlink.idx, type(curlink)) for curlink in curgroup]
533
+
534
+ elif isinstance(linked_arrays, list):
535
+
536
+ self.linked = [(curlink.idx, type(curlink)) for curlink in linked_arrays]
300
537
 
301
538
  # récupération des valeurs danbs les polygones "rivière"
302
539
  curzone = self.polygons_zone
@@ -305,12 +542,15 @@ class Polygons_Analyze(Zones):
305
542
  self.river_values={}
306
543
  for curkey, curgroup in linked_arrays.items():
307
544
  self.river_values[curkey] = curzone.get_all_values_linked_polygon(curgroup, key_idx_names='name')
545
+
308
546
  elif isinstance(linked_arrays, list):
547
+
309
548
  self.river_values = curzone.get_all_values_linked_polygon(linked_arrays, key_idx_names='name')
310
549
 
311
550
  def _get_river_heads(self,
312
551
  which_group=None):
313
552
  """Compute Head"""
553
+
314
554
  head = {}
315
555
 
316
556
  z = self.get_river_values(stored_values_unk.WATERLEVEL, which_group)
@@ -403,6 +643,11 @@ class Polygons_Analyze(Zones):
403
643
  which_value:Union[stored_values_unk,stored_values_pos],
404
644
  which_group=None,
405
645
  operator:operators=operators.MEDIAN) -> dict:
646
+ """
647
+ Get values for the river polygons with an operator
648
+
649
+ operator : MEDIAN, MIN, MAX, PERCENTILE95, PERCENTILE5, ALL
650
+ """
406
651
 
407
652
  def extract_info(vals):
408
653
  vals_ret={}
@@ -440,6 +685,32 @@ class Polygons_Analyze(Zones):
440
685
 
441
686
  return vals_ret
442
687
 
688
+ def get_s_values(self,
689
+ which_value:Union[stored_values_unk,stored_values_pos]=stored_values_unk.WATERLEVEL,
690
+ which_group:str=None,
691
+ which_sim:str=None,
692
+ operator:operators=operators.MEDIAN):
693
+ """ Get the values of the river polygons for a specific simulation """
694
+
695
+ s=[]
696
+ val=[]
697
+
698
+ myval = self.get_river_values_op(which_value, which_group, operator)
699
+
700
+ for curkey, curval in myval.items():
701
+ if len(curval)>0 and which_sim in curval.keys():
702
+ val.append(curval[which_sim])
703
+ s.append(self.polygons_curvi[curkey])
704
+
705
+ # Tri des valeurs selon l'absisse curviligne
706
+ ret = sorted(zip(s, val))
707
+
708
+ # Séparation des listes triées
709
+ s, val = zip(*ret)
710
+
711
+ return s, val
712
+
713
+
443
714
  def plot_unk(self,
444
715
  figax = None,
445
716
  which_value:Union[stored_values_unk,stored_values_pos]=stored_values_unk.WATERLEVEL,
@@ -448,6 +719,7 @@ class Polygons_Analyze(Zones):
448
719
  options:dict=None,
449
720
  label=True,
450
721
  show=False):
722
+ """ Plot the values of the river polygons """
451
723
 
452
724
  if figax is None:
453
725
  fig,ax = plt.subplots(1,1)
@@ -528,6 +800,7 @@ class Polygons_Analyze(Zones):
528
800
  which_group=None,
529
801
  operator:operators=operators.MEDIAN,
530
802
  show=False):
803
+ """ Plot the waterline """
531
804
 
532
805
  fig,ax = self.plot_unk(figax, stored_values_unk.TOPOGRAPHY, which_group, operator, options={'color':'black', 'linewidth':2}, label=False, show=False)
533
806
  figax=(fig,ax)
@@ -547,6 +820,7 @@ class Polygons_Analyze(Zones):
547
820
  which_group=None,
548
821
  operator:operators=operators.MEDIAN,
549
822
  show=False):
823
+ """ Plot the bed elevation """
550
824
 
551
825
  fig,ax = self.plot_unk(figax, stored_values_unk.TOPOGRAPHY, which_group, operator, options={'color':'black', 'linewidth':2}, label=False, show=False)
552
826
 
@@ -563,6 +837,7 @@ class Polygons_Analyze(Zones):
563
837
  which_group=None,
564
838
  operator:operators=operators.MEDIAN,
565
839
  show=False):
840
+ """ Plot the water stage /water level """
566
841
 
567
842
  fig,ax = self.plot_unk(figax, stored_values_unk.WATERLEVEL, which_group, operator, options={'color':'blue', 'linewidth':2}, show=False)
568
843
 
@@ -579,6 +854,7 @@ class Polygons_Analyze(Zones):
579
854
  which_group=None,
580
855
  operator:operators=operators.MEDIAN,
581
856
  show=False):
857
+ """ Plot the water head """
582
858
 
583
859
  fig,ax = self.plot_unk(figax, stored_values_unk.HEAD, which_group, operator, options={'color':'blue', 'linewidth':2}, show=False)
584
860
 
File without changes
@@ -0,0 +1,479 @@
1
+
2
+ from docx import Document
3
+ from docx.shared import Pt
4
+ from docx.oxml.ns import qn
5
+ from docx.shared import Inches
6
+ from docx.shared import RGBColor
7
+ from pathlib import Path
8
+ from typing import Union, List, Tuple,Literal
9
+ from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
10
+ from PIL import Image
11
+ import pandas as pd
12
+ from tempfile import NamedTemporaryFile, TemporaryDirectory
13
+ import logging
14
+ import matplotlib.pyplot as plt
15
+ from matplotlib.figure import Figure
16
+ from datetime import datetime
17
+ import os
18
+ import socket
19
+ import hashlib
20
+
21
+ from gettext import gettext as _
22
+
23
+ class RapidReport:
24
+ """
25
+ Class for creating a report 'quickly'.
26
+
27
+ It can be used in Jupyter notebooks or in scripts to create a simple report in Word format.
28
+
29
+ Word document is created with the following structure:
30
+
31
+ - Main page with title, author, date and hash of the document
32
+ - Summary (automatically generated)
33
+ - Title
34
+ - Paragraph
35
+ - Figure (numbered automatically with caption)
36
+ - Bullet list
37
+ - Table
38
+
39
+ It is not a full-fledged reporting tool with advanced functionnalities but a simple way to create a report quickly 'on-the-fly'.
40
+
41
+
42
+ Example:
43
+
44
+ ```
45
+ rapport = RapidReport('Rapport de Projet', 'Alice')
46
+
47
+ rapport.add_title('Titre Principal', level=0)
48
+ rapport.add_paragraph('Ceci est un **paragraphe** introductif avec des mots en *italique* et en **gras**.')
49
+
50
+ rapport += "Tentative d'ajout de figure vie un lien incorrect.\nPassage à la ligne"
51
+ rapport.add_figure('/path/to/image.png', 'Légende de la figure.')
52
+
53
+ rapport.add_bullet_list(['Premier élément', 'Deuxième élément', 'Troisième élément'])
54
+
55
+ rapport.add_table_from_listoflists([['Nom', 'Âge'], ['Alice', '25'], ['Bob', '30']])
56
+
57
+ rapport.save('rapport.docx')
58
+ ```
59
+
60
+ """
61
+
62
+ def __init__(self, main_title:str, author:str):
63
+
64
+ self._main_title = None
65
+ self._author = None
66
+ self._date = None
67
+
68
+ self._content = []
69
+ self._document = Document()
70
+
71
+ self._filename = None
72
+
73
+ self._idx_figure = 0
74
+
75
+ self._styles={}
76
+ self.define_default_styles()
77
+
78
+ self._has_first_page = False
79
+ self.fill_first_page(main_title, author)
80
+
81
+ def define_default_styles(self):
82
+
83
+ # Définir le style de titre
84
+ self._title_style = self._document.styles.add_style('TitleStyle', 1)
85
+ self._title_style.font.name = 'Arial'
86
+ self._title_style.font.size = Pt(20)
87
+ self._title_style.font.bold = True
88
+ self._title_style._element.rPr.rFonts.set(qn('w:eastAsia'), 'Arial')
89
+
90
+ # Définir le style de légende
91
+ self._caption_style = self._document.styles.add_style('CaptionStyle', 1)
92
+ self._caption_style.font.name = 'Arial'
93
+ self._caption_style.font.size = Pt(10)
94
+ self._caption_style.font.italic = True
95
+ self._caption_style._element.rPr.rFonts.set(qn('w:eastAsia'), 'Arial')
96
+ self._caption_style.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
97
+
98
+ # Définir le style de corps de texte
99
+ self._body_text_style = self._document.styles.add_style('BodyTextStyle', 1)
100
+ self._body_text_style.font.name = 'Arial'
101
+ self._body_text_style.font.size = Pt(12)
102
+ self._body_text_style._element.rPr.rFonts.set(qn('w:eastAsia'), 'Arial')
103
+
104
+ # Définir le style de liste à puce
105
+ self._bullet_list_style = self._document.styles.add_style('BulletListStyle', 1)
106
+ self._bullet_list_style.font.name = 'Arial'
107
+ self._bullet_list_style.font.size = Pt(12)
108
+ self._bullet_list_style._element.rPr.rFonts.set(qn('w:eastAsia'), 'Arial')
109
+ self._bullet_list_style.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
110
+ self._bullet_list_style.paragraph_format.left_indent = Inches(0.25)
111
+
112
+ self._table_grid_style = self._document.styles.add_style('TableGrid', 3)
113
+ self._table_grid_style.font.name = 'Arial'
114
+ self._table_grid_style.font.size = Pt(10)
115
+ self._table_grid_style._element.rPr.rFonts.set(qn('w:eastAsia'), 'Arial')
116
+ self._table_grid_style.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
117
+
118
+ self._figure_style = self._document.styles.add_style('FigureStyle', 1)
119
+ self._figure_style.font.name = 'Arial'
120
+ self._figure_style.font.size = Pt(10)
121
+ self._figure_style._element.rPr.rFonts.set(qn('w:eastAsia'), 'Arial')
122
+ self._figure_style.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
123
+
124
+ self._styles['TitleStyle'] = self._title_style
125
+ self._styles['CaptionStyle'] = self._caption_style
126
+ self._styles['BodyTextStyle'] = self._body_text_style
127
+ self._styles['BulletListStyle'] = self._bullet_list_style
128
+ self._styles['TableGrid'] = self._table_grid_style
129
+ self._styles['FigureStyle'] = self._figure_style
130
+
131
+ def set_font(self, fontname:str='Arial', fontsize:int=12):
132
+ """ Définir la police et la taille de la police pour les styles de texte. """
133
+
134
+ for style in self._styles.values():
135
+ style.font.name = fontname
136
+ style.font.size = Pt(fontsize)
137
+
138
+ def fill_first_page(self, main_title:str, author:str):
139
+ """
140
+ Remplir la première page du document.
141
+
142
+ Ajouter le titre, l'auteur et la date.
143
+
144
+ """
145
+
146
+ # Récupérer le nom de l'utilisateur
147
+ user_name = os.getlogin()
148
+
149
+ # Récupérer le nom de l'ordinateur
150
+ computer_name = socket.gethostname()
151
+
152
+ logo_path = Path(__file__).parent / 'wolf_report.png'
153
+
154
+ self._main_title = main_title
155
+ self._author = author
156
+ self._date = datetime.now().strftime('%d/%m/%Y')
157
+
158
+ self._document.add_heading(self._main_title, level=0)
159
+ self.add_figure(logo_path,caption=None, width=2.0)
160
+
161
+ self.add_paragraph('Ce document a été généré automatiquement par le paquet Python "wolfhece".')
162
+ self.add_paragraph(' ')
163
+ self.add_paragraph(f'Auteur : {self._author}')
164
+ self.add_paragraph(f'Date : {self._date}')
165
+ self.add_paragraph(' ')
166
+ self.add_paragraph(f'Utilisateur : {user_name}')
167
+ self.add_paragraph(f'Ordinateur : {computer_name}')
168
+ self.add_paragraph(' ')
169
+
170
+ chain_hash = hashlib.md5(self._main_title.encode() +
171
+ self._author.encode() +
172
+ user_name.encode() +
173
+ computer_name.encode()+
174
+ self._date.encode()).hexdigest()
175
+
176
+ self.add_paragraph('Hash du document : ' + chain_hash)
177
+
178
+ self.add_new_page()
179
+
180
+ self.add_paragraph('summary')
181
+
182
+ self._has_first_page = True
183
+
184
+ def add_title(self, title:str, level:int=1):
185
+ """ Ajoute un titre au document. """
186
+
187
+ self._content.append(('title', title, level))
188
+
189
+ def _list_titles(self, level:int=None):
190
+ """ Renvoie la liste des titres du document. """
191
+
192
+ if level is None:
193
+ return [item[1] for item in self._content if item[0] == 'title']
194
+ else:
195
+ return [item[1] for item in self._content if item[0] == 'title' and item[2] == level]
196
+
197
+ def _list_captions(self):
198
+ """ Renvoie la liste des légendes de figures du document. """
199
+
200
+ return [item[2] for item in self._content if item[0] == 'figure' if item[2]]
201
+
202
+ def _list_figures(self):
203
+ """ Renvoie la liste des figures du document. """
204
+
205
+ return [item[1] for item in self._content if item[0] == 'figure' if item[1] and item[2]]
206
+
207
+ def _list_index(self):
208
+ """ Renvoie la liste des index de figures du document. """
209
+
210
+ return [item[3] for item in self._content if item[0] == 'figure' if item[3]]
211
+
212
+ def fig_exists(self, fig_name:str):
213
+ """ Vérifie si une figure existe dans le document. """
214
+
215
+ return fig_name in self._list_figures()
216
+
217
+ def get_fig_index(self, fig_name_caption:str):
218
+ """ Renvoie la légende d'une figure. """
219
+
220
+ list_figures = self._list_figures()
221
+ list_captions = self._list_captions()
222
+
223
+ if fig_name_caption in list_figures:
224
+ idx = self._list_figures().index(fig_name_caption)+1
225
+ elif fig_name_caption in list_captions:
226
+ idx = self._list_captions().index(fig_name_caption)+1
227
+ else:
228
+ idx = None
229
+
230
+ return idx
231
+
232
+ def _add_summary(self):
233
+ """ Ajoute un sommaire au document. """
234
+
235
+ titles = self._list_titles()
236
+
237
+ self._document.add_heading(_('Summary'), level=1).style = 'TitleStyle'
238
+
239
+ for cur_title in titles:
240
+ p = self._document.add_paragraph(cur_title, style='BodyTextStyle')
241
+ run = p.add_run()
242
+ run.add_tab()
243
+ run.bold = True
244
+ p.style = 'BodyTextStyle'
245
+
246
+ self._document.add_heading(_('List of figures'), level=1).style = 'TitleStyle'
247
+ figures = self._list_captions()
248
+ for i, cur_figure in enumerate(figures):
249
+ p = self._document.add_paragraph(f'Fig. {i+1} : {cur_figure}', style='BodyTextStyle')
250
+ run = p.add_run()
251
+ run.add_tab()
252
+ run.bold = True
253
+ p.style = 'BodyTextStyle'
254
+
255
+ self._document.add_page_break()
256
+
257
+ def add_paragraph(self, paragraph_text:str, style:str='BodyTextStyle'):
258
+ """ Ajoute un paragraphe au document. """
259
+
260
+ self._content.append(('paragraph', paragraph_text, style))
261
+
262
+ def add(self, paragraph_text:str, style:str='BodyTextStyle'):
263
+ """ Ajoute un paragraphe au document. """
264
+
265
+ self.add_paragraph(paragraph_text, style=style)
266
+
267
+ def __add__(self, paragraph_text:str):
268
+ """ Surcharge de l'opérateur + pour ajouter un paragraphe. """
269
+
270
+ self.add_paragraph(paragraph_text)
271
+
272
+ return self
273
+
274
+ def add_figure(self, image_path:Union[str,Path,Image.Image], caption:str, width:float=7.0):
275
+ """ Ajoute une figure au document avec une légende. """
276
+
277
+ if caption:
278
+ self._idx_figure += 1
279
+
280
+ self._content.append(('figure', image_path, caption, width, self._idx_figure))
281
+
282
+ def add_bullet_list(self, bullet_list: List[str], style:str='BulletListStyle'):
283
+ """ Ajoute une liste à puce au document. """
284
+
285
+ for item in bullet_list:
286
+ self.add_paragraph('- ' + item, style=style)
287
+
288
+ def add_new_page(self):
289
+ """ Ajoute une nouvelle page au document. """
290
+
291
+ self._content.append(('newpage', '', None))
292
+
293
+ def add_table_from_listoflists(self, data:List[List[str]], style:str='TableGrid'):
294
+ """
295
+ Ajoute un tableau au document.
296
+
297
+ :param data: Liste de listes contenant les données du tableau. Chaque liste est une ligne du tableau.
298
+
299
+ """
300
+
301
+ self._content.append(('table', data, style))
302
+
303
+ def add_table_from_dict(self, data:dict, style:str='TableGrid'):
304
+ """
305
+ Ajoute un tableau au document.
306
+
307
+ :param data: Dictionnaire contenant les données du tableau. Les clés sont les en-têtes de colonnes.
308
+
309
+ """
310
+
311
+ table_data = [list(data.keys())]
312
+ table_data += [list(data.values())]
313
+ self.add_table_from_listoflists(table_data, style=style)
314
+
315
+ def add_table_as_picture(self, data:Union[List[List[str]], dict, pd.DataFrame, Figure], caption:str=None):
316
+ """ Ajoute un tableau au document sous forme d'image. """
317
+
318
+ def fig2img(fig):
319
+ """Convert a Matplotlib figure to a PIL Image and return it"""
320
+ import io
321
+ buf = io.BytesIO()
322
+
323
+ fig.savefig(buf, bbox_inches='tight')
324
+ buf.seek(0)
325
+ img = Image.open(buf)
326
+ return img
327
+
328
+ if isinstance(data, Figure):
329
+ tmp_image = fig2img(data)
330
+ self.add_figure(tmp_image, caption)
331
+ return
332
+
333
+ if isinstance(data, dict):
334
+ data = pd.DataFrame(data)
335
+ elif isinstance(data, list):
336
+ data = pd.DataFrame(data)
337
+
338
+ fig, ax = plt.subplots()
339
+
340
+ ax.axis('off')
341
+ ax.table(cellText=data.values,
342
+ colLabels=data.columns,
343
+ loc='center',
344
+ cellLoc='center',
345
+ colColours=['#f3f3f3']*len(data.columns))
346
+
347
+ fig.tight_layout()
348
+
349
+ tmp_image = fig2img(fig)
350
+
351
+ self.add_figure(tmp_image, caption, width=4.0)
352
+
353
+ def _apply_text_styles(self, paragraph, text):
354
+ """ Search for bold and italic styles in the text and apply them."""
355
+
356
+ def split_bold(text):
357
+ return text.split('**')
358
+
359
+ def split_italic(text):
360
+ return text.split('*')
361
+
362
+ splitted_bold = split_bold(text)
363
+
364
+ bold = False
365
+ for cur_text in splitted_bold:
366
+ if cur_text != '':
367
+ italic = False
368
+ spliited_italic = split_italic(cur_text)
369
+ for cur_text2 in spliited_italic:
370
+ if cur_text2 != '':
371
+ run = paragraph.add_run(cur_text2)
372
+ run.bold = bold
373
+ run .italic = italic
374
+
375
+ italic = not italic
376
+ bold = not bold
377
+
378
+ def parse_content(self):
379
+ """ Parse le contenu du document et l'ajoute au document Word. """
380
+
381
+ # tmp_dir = TemporaryDirectory()
382
+
383
+ for item in self._content:
384
+
385
+ if item[0] == 'title':
386
+ self._document.add_heading(item[1], level=item[2]).style = 'TitleStyle'
387
+
388
+ elif item[0] == 'paragraph':
389
+
390
+ if item[1] == 'summary':
391
+ self._add_summary()
392
+ continue
393
+ else:
394
+ p = self._document.add_paragraph()
395
+ self._apply_text_styles(p, item[1])
396
+ p.style = item[2] if item[2] else 'BodyTextStyle'
397
+
398
+ elif item[0] == 'figure':
399
+
400
+ if isinstance(item[1], Image.Image):
401
+
402
+ tmp_name = NamedTemporaryFile(suffix='.png').name
403
+ item[1].save(tmp_name)
404
+
405
+ elif isinstance(item[1], str):
406
+ tmp_name = item[1]
407
+
408
+ elif isinstance(item[1], Path):
409
+ tmp_name = str(item[1])
410
+
411
+ if Path(tmp_name).exists():
412
+ self._document.add_picture(tmp_name, width=Inches(item[3]) if item[3] else Inches(7.0))
413
+ self._document.paragraphs[-1].style = 'FigureStyle'
414
+ else:
415
+ logging.error(f"File {tmp_name} not found.")
416
+ p = self._document.add_paragraph()
417
+ run = p.add_run(f'Error: Image not found. {tmp_name}')
418
+ run.font.color.rgb = RGBColor(255, 0, 0)
419
+ p.style = 'BodyTextStyle'
420
+
421
+ if item[2]:
422
+ caption = self._document.add_paragraph(f'Fig. {item[4]} :' + item[2])
423
+ caption.style = 'CaptionStyle'
424
+
425
+ elif item[0] == 'table':
426
+
427
+ data = item[1]
428
+ style = item[2]
429
+ table = self._document.add_table(rows=len(data), cols=len(data[0]))
430
+ table.style = style
431
+
432
+ for i, row in enumerate(data):
433
+ for j, cell in enumerate(row):
434
+ table.cell(i, j).text = cell
435
+
436
+ elif item[0] == 'newpage':
437
+ self._document.add_page_break()
438
+
439
+ def save(self, file_path:Union[str,Path]=None):
440
+ """ Sauvegarde le document Word. """
441
+
442
+ if file_path is None:
443
+ file_path = self._filename
444
+
445
+ if file_path is None:
446
+ raise ValueError("Le chemin du fichier n'a pas été spécifié.")
447
+
448
+ self.parse_content()
449
+ try:
450
+ self._document.save(str(file_path))
451
+ except Exception as e:
452
+ logging.error(f"Error saving file: {e}")
453
+
454
+ if __name__ == '__main__':
455
+
456
+ # Exemple d'utilisation
457
+ rapport = RapidReport('Rapport de Projet', 'Alice')
458
+
459
+ rapport.add_title('Titre Principal', level=0)
460
+ rapport.add_paragraph('Ceci est un **paragraphe** introductif avec des mots en *italique* et en **gras**.')
461
+
462
+ rapport += "Tentative d'ajout de figure vie un lien incorrect.\nPassage à la ligne"
463
+ rapport.add_figure('/path/to/image.png', 'Légende de la figure.')
464
+ rapport+="""
465
+ Commentraire sur la figure multilignes
466
+ ligne 2
467
+ ligne3"""
468
+
469
+ rapport.add_bullet_list(['Premier élément', 'Deuxième élément', 'Troisième élément'])
470
+
471
+ rapport.add_table_from_listoflists([['Nom', 'Âge'], ['Alice', '25'], ['Bob', '30']])
472
+ rapport.add_table_from_dict({'Nom': ['Alice', 'Bob'], 'Âge': ['25', '30']})
473
+ rapport.add_table_as_picture({'Nom': ['Alice', 'Bob'], 'Âge': ['25', '30']}, caption='Tableau de données')
474
+
475
+ rapport.save('rapport.docx')
476
+
477
+ assert rapport.get_fig_index('/path/to/image.png') == 1
478
+ assert rapport.get_fig_index('Tableau de données') == 2
479
+
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wolfhece
3
- Version: 2.1.7
3
+ Version: 2.1.9
4
4
  Author-email: Pierre Archambeau <pierre.archambeau@uliege.be>
5
5
  License: AGPL-v3 License
6
6
  Project-URL: Homepage, https://uee.uliege.be/hece
@@ -11,7 +11,7 @@ wolfhece/PyGui.py,sha256=VKE785z9XLIWNbxqpyEceLK_wtmPJyq6A_M_qX_94Lg,104772
11
11
  wolfhece/PyGuiHydrology.py,sha256=wKhR-KthPRyzJ887NmsozmUpm2CIQIwO3IbYORCYjrE,7290
12
12
  wolfhece/PyHydrographs.py,sha256=GKK8U0byI45H9O_e4LAOOi7Aw0Tg7Q0Lx322stPg5IQ,3453
13
13
  wolfhece/PyPalette.py,sha256=_Nm2Lc4UxYlZgK8ifZDioG8a0at8oiteYC0x_4XugFc,24384
14
- wolfhece/PyParams.py,sha256=361iy9b9zTjoPCj9gh8-OIo0TBW5laLG87AkWE6f_eg,96290
14
+ wolfhece/PyParams.py,sha256=963fkDSEadlLLxe7WM4Nl6nLOeZsOWSFmfg7hIsWPU4,96785
15
15
  wolfhece/PyPictures.py,sha256=-mJB0JL2YYiEK3D7_ssDkvYiMWK4ve9kXhozQXNeSx8,2216
16
16
  wolfhece/PyTranslate.py,sha256=4appkmNeHHZLFmUtaA_k5_5QL-5ymxnbVN4R2OblmtE,622
17
17
  wolfhece/PyVertex.py,sha256=dHTjyYYTn0F_NWerlAOBKHV79RUzEEtMJMldQtVc1Cs,40092
@@ -34,13 +34,13 @@ wolfhece/import_ascfiles.py,sha256=jg4urcLdSgFS1Knvh7AVGJqM44qc_uYDNrR568tMh-A,4
34
34
  wolfhece/ins.py,sha256=0aU1mo4tYbw64Gwzrqbh-NCTH1tukmk0mpPHjRPHZXU,12661
35
35
  wolfhece/irm_qdf.py,sha256=749SlAXiN1oXp5tfBJoPNJWxydQlY55K0qvIM5YexlM,15436
36
36
  wolfhece/ismember.py,sha256=fkLvaH9fhx-p0QrlEzqa6ySO-ios3ysjAgXVXzLgSpY,2482
37
- wolfhece/multiprojects.py,sha256=DdObaugeNrd2I7rtWd6M5W0nVXN8KRVmcXLjMg9oqYs,15795
37
+ wolfhece/multiprojects.py,sha256=tHrAdOBoEEsuNS8hV7udbvduOZLj_NqF3ee5hSDBhN8,20903
38
38
  wolfhece/picc.py,sha256=KKPNk1BEe7QBzo2icIsdsxUopJ1LXYTomfdfeG2gCeA,7419
39
39
  wolfhece/pyGui1D.py,sha256=pzLWXQ_w3Y_yI846w1GklFO9h5lWZOqiUzg1BUPkuRI,121616
40
- wolfhece/pybridges.py,sha256=HJ1BL1HC7UrgpQ-3jKXkqPFmc-TzToL28Uex2hjHk6c,57166
40
+ wolfhece/pybridges.py,sha256=4PRSsWngDmQnlVuN2tJj0C_HT1h47ExH9QTUPs_Wxlg,57215
41
41
  wolfhece/pydike.py,sha256=G4jfSZaAHHr4VWEJqnXSvEswXvlOz1yhbhQ6uu3AqyM,1943
42
42
  wolfhece/pylogging.py,sha256=i9Zugx3t9dPc7nBwcP20L_R4_k_WawpAQsvbZU8l9Hg,4230
43
- wolfhece/pypolygons_scen.py,sha256=BAo1tg8rJGkmKlEyTbb8gnofk-aPbrKunE2-0YWNaHQ,26581
43
+ wolfhece/pypolygons_scen.py,sha256=Zqr_vs--QEKRSxG-SfduqSR0aPT5rM3BRxmXTOSh2Iw,37146
44
44
  wolfhece/pyshields.py,sha256=YS6VVjjzoA-ZR6YRccqjMcW3McNqNLoQODC6TNNkmPw,22983
45
45
  wolfhece/pyviews.py,sha256=hYdyrEvWF48dGBDOLIwmC28C0L8I28U4ohXk9nltF94,9666
46
46
  wolfhece/pywalous.py,sha256=GRS5TKoVT0J-3PBqMdlyFRHrqkdYC-uHgDPcMjoDEZw,18674
@@ -66,7 +66,7 @@ wolfhece/apps/check_install.py,sha256=jrKR-njqnpIh6ZJqvP6KbDUPVCfwTNQj4glQhcyzs9
66
66
  wolfhece/apps/curvedigitizer.py,sha256=avWERHuVxPnJBOD_ibczwW_XG4vAenqWS8W1zjhBox8,4898
67
67
  wolfhece/apps/isocurrent.py,sha256=4XnNWPa8mYUK7V4zdDRFrHFIXNG2AN2og3TqWKKcqjY,3811
68
68
  wolfhece/apps/splashscreen.py,sha256=LkEVMK0eCc84NeCWD3CGja7fuQ_k1PrZdyqD3GQk_8c,2118
69
- wolfhece/apps/version.py,sha256=QnnuNPQqjbCKaqce2cE43Ky9-bjQgDRUR3St4eC7HII,387
69
+ wolfhece/apps/version.py,sha256=FOnRXwi0LTd0u8m3r5Ap-8eX-pLzJQIG3E9iImOCttA,387
70
70
  wolfhece/apps/wolf.py,sha256=gqfm-ZaUJqNsfCzmdtemSeqLw-GVdSVix-evg5WArJI,293
71
71
  wolfhece/apps/wolf2D.py,sha256=gWD9ee2-1pw_nUxjgRaJMuSe4kUT-RWhOeoTt_Lh1mM,267
72
72
  wolfhece/apps/wolf_logo.bmp,sha256=ruJ4MA51CpGO_AYUp_dB4SWKHelvhOvd7Q8NrVOjDJk,3126
@@ -241,6 +241,9 @@ wolfhece/radar/wolfradar.py,sha256=ylyz8hNAGq92WXnMO8hVe6Nk-a7g--fL-xOcjfhFEtk,9
241
241
  wolfhece/rem/REMMaker.py,sha256=kffClHHpf8P4ruZpEb9EB__HBzg9rFAkiVCh-GFtIHU,30790
242
242
  wolfhece/rem/RasterViz.py,sha256=TDhWyMppcYBL71HfhpZuMgYKhz7faZg-MEOQJo_3Ivo,29128
243
243
  wolfhece/rem/__init__.py,sha256=S2-J5uEGK_VaMFjRUYFIdSScJjZyuXH4RmMmnG3OG7I,19
244
+ wolfhece/report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
245
+ wolfhece/report/reporting.py,sha256=ptsObq4hrVc1b_cfu2ISL6oesSsBphk03QtFnr8E9hc,17565
246
+ wolfhece/report/wolf_report.png,sha256=NoSV58LSwb-oxCcZScRiJno-kxDwRdm_bK-fiMsKJdA,592485
244
247
  wolfhece/scenario/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
245
248
  wolfhece/scenario/check_scenario.py,sha256=nFiCscEGHyz1YvjmZoKlYrfmW03-nLiDTDdRoeE6MUs,4619
246
249
  wolfhece/scenario/config_manager.py,sha256=SValpikuNt_XaJXt8P7kAu6iN_N5YlohyL8ry3iT380,77389
@@ -264,8 +267,8 @@ wolfhece/sounds/sonsw2.wav,sha256=pFLVt6By0_EPQNt_3KfEZ9a1uSuYTgQSX1I_Zurv9Rc,11
264
267
  wolfhece/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
265
268
  wolfhece/ui/wolf_multiselection_collapsiblepane.py,sha256=yGbU_JsF56jsmms0gh7mxa7tbNQ_SxqhpAZxhm-mTy4,14860
266
269
  wolfhece/ui/wolf_times_selection_comparison_models.py,sha256=wCxGRnE3kzEkWlWA6-3X8ADOFux_B0a5QWJ2GnXTgJw,4709
267
- wolfhece-2.1.7.dist-info/METADATA,sha256=tv3E_t--2aA71zEbF4xRjwE3klu3AAVpHUIzRBLFVH8,2281
268
- wolfhece-2.1.7.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
269
- wolfhece-2.1.7.dist-info/entry_points.txt,sha256=AIu1KMswrdsqNq_2jPtrRIU4tLjuTnj2dCY-pxIlshw,276
270
- wolfhece-2.1.7.dist-info/top_level.txt,sha256=EfqZXMVCn7eILUzx9xsEu2oBbSo9liWPFWjIHik0iCI,9
271
- wolfhece-2.1.7.dist-info/RECORD,,
270
+ wolfhece-2.1.9.dist-info/METADATA,sha256=kTZzCdCRgXrjJ2HsS_7YS8fXP8zYlxvXNigdrYepDqg,2281
271
+ wolfhece-2.1.9.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
272
+ wolfhece-2.1.9.dist-info/entry_points.txt,sha256=AIu1KMswrdsqNq_2jPtrRIU4tLjuTnj2dCY-pxIlshw,276
273
+ wolfhece-2.1.9.dist-info/top_level.txt,sha256=EfqZXMVCn7eILUzx9xsEu2oBbSo9liWPFWjIHik0iCI,9
274
+ wolfhece-2.1.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (70.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5