wolfhece 2.2.27__py3-none-any.whl → 2.2.28__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 +10 -2
- wolfhece/PyPalette.py +18 -0
- wolfhece/PyVertexvectors.py +1 -4
- wolfhece/apps/version.py +1 -1
- wolfhece/report/common.py +496 -0
- wolfhece/report/compare_arrays.py +844 -0
- wolfhece/report/simplesimgpu.py +1 -95
- wolfhece/report/tools.py +121 -16
- wolfhece/scenario/config_manager.py +22 -1
- wolfhece/wolf_array.py +49 -2
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.28.dist-info}/METADATA +1 -1
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.28.dist-info}/RECORD +15 -13
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.28.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.28.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.27.dist-info → wolfhece-2.2.28.dist-info}/top_level.txt +0 -0
wolfhece/PyDraw.py
CHANGED
@@ -4300,12 +4300,20 @@ class WolfMapViewer(wx.Frame):
|
|
4300
4300
|
if self.active_array is not None:
|
4301
4301
|
return self.active_array.mypal
|
4302
4302
|
else:
|
4303
|
-
|
4303
|
+
if self.active_res2d is not None:
|
4304
|
+
logging.warning(_('No active array -- Using active 2D result palette instead'))
|
4305
|
+
return self.active_res2d.mypal
|
4306
|
+
else:
|
4307
|
+
return wolfpalette()
|
4304
4308
|
elif act_res2d:
|
4305
4309
|
if self.active_res2d is not None:
|
4306
4310
|
return self.active_res2d.mypal
|
4307
4311
|
else:
|
4308
|
-
|
4312
|
+
if self.active_array is not None:
|
4313
|
+
logging.warning(_('No active 2D result -- Using active array palette instead'))
|
4314
|
+
return self.active_array.mypal
|
4315
|
+
else:
|
4316
|
+
return wolfpalette()
|
4309
4317
|
else:
|
4310
4318
|
return wolfpalette()
|
4311
4319
|
|
wolfhece/PyPalette.py
CHANGED
@@ -278,6 +278,11 @@ class wolfpalette(wx.Frame, LinearSegmentedColormap):
|
|
278
278
|
logging.warning('No values in palette - Nothing to do !')
|
279
279
|
return None, None
|
280
280
|
|
281
|
+
if len(self.values) ==0:
|
282
|
+
logging.warning('No values in palette - Nothing to do !')
|
283
|
+
logging.info(_('Do you have defined the palette values ?'))
|
284
|
+
logging.info(_('If yes, please check your Global Options. You may not have defined the correct palette to use.'))
|
285
|
+
return None, None
|
281
286
|
|
282
287
|
if fn == '':
|
283
288
|
file = wx.FileDialog(None, "Choose .pal file", wildcard="png (*.png)|*.png|all (*.*)|*.*", style=wx.FD_SAVE)
|
@@ -288,6 +293,7 @@ class wolfpalette(wx.Frame, LinearSegmentedColormap):
|
|
288
293
|
fn = file.GetPath()
|
289
294
|
|
290
295
|
if h_or_v == 'v':
|
296
|
+
|
291
297
|
if figax is None:
|
292
298
|
fig, ax = plt.subplots(1, 1)
|
293
299
|
else:
|
@@ -636,6 +642,18 @@ class wolfpalette(wx.Frame, LinearSegmentedColormap):
|
|
636
642
|
|
637
643
|
self.fill_segmentdata()
|
638
644
|
|
645
|
+
def default_difference3(self):
|
646
|
+
""" Palette 3 couleurs pour les différences par défaut dans WOLF """
|
647
|
+
self.nb = 3
|
648
|
+
self.values = np.asarray([-10., 0., 10.], dtype=np.float64)
|
649
|
+
self.colors = np.zeros((self.nb, 4), dtype=int)
|
650
|
+
self.colorsflt = np.zeros((self.nb, 4), dtype=np.float64)
|
651
|
+
self.colors[0, :] = [0, 0, 255, 255] # Bleu
|
652
|
+
self.colors[1, :] = [255, 255, 255, 255] # Blanc
|
653
|
+
self.colors[2, :] = [255, 0, 0, 255] # Rouge
|
654
|
+
self.fill_segmentdata()
|
655
|
+
|
656
|
+
|
639
657
|
def set_values_colors(self,
|
640
658
|
values: typing.Union[list[float], np.ndarray],
|
641
659
|
colors: typing.Union[list[tuple[int]], np.ndarray]):
|
wolfhece/PyVertexvectors.py
CHANGED
@@ -4111,7 +4111,7 @@ class zone:
|
|
4111
4111
|
else:
|
4112
4112
|
mypl = vector(name=self.active_vector.myname+"_duplicate")
|
4113
4113
|
mypl.myvertices = [wolfvertex(cur.x,cur.y,cur.z) for cur in self.active_vector.myvertices]
|
4114
|
-
|
4114
|
+
|
4115
4115
|
|
4116
4116
|
if mypl is None:
|
4117
4117
|
return
|
@@ -4567,7 +4567,6 @@ class zone:
|
|
4567
4567
|
vr = sublsr.myvertices.copy()
|
4568
4568
|
vr.reverse()
|
4569
4569
|
curvec.myvertices = sublsl.myvertices.copy() + vr
|
4570
|
-
# curvec.nbvertices = len(curvec.myvertices) FIXME Not needed anymore
|
4571
4570
|
for curv in curvec.myvertices:
|
4572
4571
|
curv.z = smean
|
4573
4572
|
else:
|
@@ -4631,8 +4630,6 @@ class zone:
|
|
4631
4630
|
curv.z = smean
|
4632
4631
|
for curv in curvecright.myvertices:
|
4633
4632
|
curv.z = smean
|
4634
|
-
curvecleft.nbvertices = len(curvecleft.myvertices)
|
4635
|
-
curvecright.nbvertices = len(curvecright.myvertices)
|
4636
4633
|
else:
|
4637
4634
|
#left poly
|
4638
4635
|
if sublsl.geom_type=='Point':
|
wolfhece/apps/version.py
CHANGED
@@ -0,0 +1,496 @@
|
|
1
|
+
import sys
|
2
|
+
import wx
|
3
|
+
import os
|
4
|
+
import platform
|
5
|
+
|
6
|
+
import pymupdf as pdf
|
7
|
+
from tempfile import NamedTemporaryFile
|
8
|
+
from tempfile import TemporaryDirectory
|
9
|
+
|
10
|
+
import matplotlib.pyplot as plt
|
11
|
+
import numpy as np
|
12
|
+
import logging
|
13
|
+
from pathlib import Path
|
14
|
+
from datetime import datetime as dt
|
15
|
+
|
16
|
+
from .. import __version__ as wolfhece_version
|
17
|
+
try:
|
18
|
+
from wolfgpu.version import __version__ as wolfgpu_version
|
19
|
+
except ImportError:
|
20
|
+
wolfgpu_version = "not installed"
|
21
|
+
|
22
|
+
from ..PyVertexvectors import vector, zone, Zones, wolfvertex as wv
|
23
|
+
from ..PyTranslate import _
|
24
|
+
|
25
|
+
def pts2cm(pts):
|
26
|
+
""" Convert points to centimeters for PyMuPDF.
|
27
|
+
|
28
|
+
One point equals 1/72 inches.
|
29
|
+
"""
|
30
|
+
return pts / 28.346456692913385 # 1 point = 1/28.346456692913385 cm = 2.54/72
|
31
|
+
|
32
|
+
def pt2inches(pts):
|
33
|
+
""" Convert points to inches for PyMuPDF.
|
34
|
+
|
35
|
+
One point equals 1/72 inches.
|
36
|
+
"""
|
37
|
+
return pts / 72.0 # 1 point = 1/72 inches
|
38
|
+
|
39
|
+
def inches2cm(inches):
|
40
|
+
""" Convert inches to centimeters.
|
41
|
+
|
42
|
+
One inch equals 2.54 centimeters.
|
43
|
+
"""
|
44
|
+
return inches * 2.54 # 1 inch = 2.54 cm
|
45
|
+
|
46
|
+
def cm2pts(cm):
|
47
|
+
""" Convert centimeters to points for PyMuPDF.
|
48
|
+
|
49
|
+
One point equals 1/72 inches.
|
50
|
+
"""
|
51
|
+
return cm * 28.346456692913385 # 1 cm = 28.346456692913385 points = 72/2.54
|
52
|
+
|
53
|
+
def cm2inches(cm):
|
54
|
+
""" Convert centimeters to inches.
|
55
|
+
|
56
|
+
One inch equals 2.54 centimeters.
|
57
|
+
"""
|
58
|
+
return cm / 2.54 # 1 cm = 1/2.54 inches
|
59
|
+
|
60
|
+
def A4_rect():
|
61
|
+
""" Return the A4 rectangle in PyMuPDF units.
|
62
|
+
|
63
|
+
(0, 0) is the top-left corner in PyMuPDF coordinates.
|
64
|
+
"""
|
65
|
+
return pdf.Rect(0, 0, cm2pts(21), cm2pts(29.7)) # A4 size in points (PDF units)
|
66
|
+
|
67
|
+
def rect_cm(x, y, width, height):
|
68
|
+
""" Create a rectangle in PyMuPDF units from centimeters.
|
69
|
+
|
70
|
+
(0, 0) is the top-left corner in PyMuPDF coordinates.
|
71
|
+
"""
|
72
|
+
return pdf.Rect(cm2pts(x), cm2pts(y), cm2pts(x) + cm2pts(width), cm2pts(y) + cm2pts(height))
|
73
|
+
|
74
|
+
def get_rect_from_text(text, width, fontsize=10, padding=5):
|
75
|
+
""" Get a rectangle that fits the text in PyMuPDF units.
|
76
|
+
|
77
|
+
:param text: The text to fit in the rectangle.
|
78
|
+
:param width: The width of the rectangle in centimeters.
|
79
|
+
:param fontsize: The font size in points.
|
80
|
+
:param padding: Padding around the text in points.
|
81
|
+
:return: A PyMuPDF rectangle that fits the text.
|
82
|
+
"""
|
83
|
+
# Create a temporary PDF document to measure the text size
|
84
|
+
with NamedTemporaryFile(delete=True, suffix='.pdf') as temp_pdf:
|
85
|
+
doc = pdf.Document()
|
86
|
+
page = doc.new_page(A4_rect())
|
87
|
+
text_rect = page.insert_text((0, 0), text, fontsize=fontsize, width=cm2pts(width))
|
88
|
+
doc.save(temp_pdf.name)
|
89
|
+
|
90
|
+
# Get the size of the text rectangle
|
91
|
+
text_width = text_rect.width + padding * 2
|
92
|
+
text_height = text_rect.height + padding * 2
|
93
|
+
# Create a rectangle with the specified width and height
|
94
|
+
rect = pdf.Rect(0, 0, cm2pts(width), text_height)
|
95
|
+
# Adjust the rectangle to fit the text
|
96
|
+
rect.x0 -= padding
|
97
|
+
rect.y0 -= padding
|
98
|
+
rect.x1 += padding
|
99
|
+
rect.y1 += padding
|
100
|
+
return rect
|
101
|
+
|
102
|
+
|
103
|
+
def list_to_html(list_items, font_size="10pt", font_family="Helvetica"):
|
104
|
+
# Génère le CSS
|
105
|
+
css = f"""
|
106
|
+
p {{font-size:{font_size};
|
107
|
+
font-family:{font_family};
|
108
|
+
color:#BEBEBE;
|
109
|
+
align-text:center}}
|
110
|
+
|
111
|
+
ul.list {{
|
112
|
+
font-size: {font_size};
|
113
|
+
font-family: {font_family};
|
114
|
+
color: #2C3E50;
|
115
|
+
padding-left: 20px;
|
116
|
+
}}
|
117
|
+
li {{
|
118
|
+
margin-bottom: 5px;
|
119
|
+
}}
|
120
|
+
"""
|
121
|
+
|
122
|
+
# Génère le HTML
|
123
|
+
html = "<ul class='list'>"
|
124
|
+
for item in list_items:
|
125
|
+
html += f" <li>{item}</li>\n"
|
126
|
+
html += "</ul>"
|
127
|
+
|
128
|
+
css = css.replace('\n', ' ') # Remove newlines in CSS for better readability
|
129
|
+
|
130
|
+
return html, css
|
131
|
+
|
132
|
+
|
133
|
+
def list_to_html_aligned(list_items, font_size="10pt", font_family="Helvetica"):
|
134
|
+
# Génère le CSS
|
135
|
+
css = f"""
|
136
|
+
p {{font-size:{font_size};
|
137
|
+
font-family:{font_family};
|
138
|
+
color:#BEBEBE;
|
139
|
+
align-text:left}}
|
140
|
+
|
141
|
+
div.list {{
|
142
|
+
font-size: {font_size};
|
143
|
+
font-family: {font_family};
|
144
|
+
color: #2C3E50;
|
145
|
+
padding-left: 8px;
|
146
|
+
align-text:left
|
147
|
+
}}
|
148
|
+
li {{
|
149
|
+
margin-bottom: 5px;
|
150
|
+
}}
|
151
|
+
"""
|
152
|
+
|
153
|
+
# Génère le HTML
|
154
|
+
html = "<div class='list'>"
|
155
|
+
html += " - ".join(list_items) # Join the items with a hyphen
|
156
|
+
html += "</div>"
|
157
|
+
|
158
|
+
css = css.replace('\n', ' ') # Remove newlines in CSS for better readability
|
159
|
+
|
160
|
+
return html, css
|
161
|
+
|
162
|
+
# A4 format
|
163
|
+
PAGE_WIDTH = 21 # cm
|
164
|
+
PAGE_HEIGHT = 29.7 # cm
|
165
|
+
|
166
|
+
# Default Powerpoint 16:9 slide dimensions
|
167
|
+
SLIDE_HEIGHT = inches2cm(7.5) # cm
|
168
|
+
SLIDE_WIDTH = inches2cm(13.3333) # cm
|
169
|
+
|
170
|
+
class DefaultLayoutA4(Zones):
|
171
|
+
"""
|
172
|
+
Enum for default layout options.
|
173
|
+
"""
|
174
|
+
|
175
|
+
def __init__(self, title:str, filename = '', ox = 0, oy = 0, tx = 0, ty = 0, parent=None, is2D=True, idx = '', plotted = True, mapviewer=None, need_for_wx = False, bbox = None, find_minmax = True, shared = False, colors = None):
|
176
|
+
super().__init__(filename, ox, oy, tx, ty, parent, is2D, idx, plotted, mapviewer, need_for_wx, bbox, find_minmax, shared, colors)
|
177
|
+
|
178
|
+
self.title = title
|
179
|
+
|
180
|
+
self.left_right_margin = 1 # cm
|
181
|
+
self.top_bottom_margin = 0.5 # cm
|
182
|
+
self.padding = 0.5 # cm
|
183
|
+
|
184
|
+
WIDTH_TITLE = 16 # cm
|
185
|
+
HEIGHT_TITLE = 1.5 # cm
|
186
|
+
|
187
|
+
WIDTH_VERSIONS = 16 # cm
|
188
|
+
HEIGHT_VERSIONS = .5 # cm
|
189
|
+
|
190
|
+
X_LOGO = 18.5 # Logo starts after the title and versions
|
191
|
+
WIDTH_LOGO = 1.5 # cm
|
192
|
+
HEIGHT_LOGO = 1.5 # cm
|
193
|
+
|
194
|
+
HEIGHT_FOOTER = 1.2 # cm
|
195
|
+
|
196
|
+
page = zone(name='Page')
|
197
|
+
elts = zone(name='Elements')
|
198
|
+
|
199
|
+
self.add_zone(page, forceparent= True)
|
200
|
+
self.add_zone(elts, forceparent= True)
|
201
|
+
|
202
|
+
vec_page = vector(name=_("Page"))
|
203
|
+
vec_page.add_vertex(wv(0, 0))
|
204
|
+
vec_page.add_vertex(wv(PAGE_WIDTH, 0))
|
205
|
+
vec_page.add_vertex(wv(PAGE_WIDTH, PAGE_HEIGHT))
|
206
|
+
vec_page.add_vertex(wv(0, PAGE_HEIGHT))
|
207
|
+
vec_page.force_to_close()
|
208
|
+
page.add_vector(vec_page, forceparent=True)
|
209
|
+
|
210
|
+
vec_title = vector(name=_("Title"))
|
211
|
+
y_from_top = PAGE_HEIGHT - self.top_bottom_margin
|
212
|
+
vec_title.add_vertex(wv(self.left_right_margin, y_from_top))
|
213
|
+
vec_title.add_vertex(wv(self.left_right_margin + WIDTH_TITLE, y_from_top))
|
214
|
+
vec_title.add_vertex(wv(self.left_right_margin + WIDTH_TITLE, y_from_top - HEIGHT_TITLE))
|
215
|
+
vec_title.add_vertex(wv(self.left_right_margin, y_from_top - HEIGHT_TITLE))
|
216
|
+
vec_title.force_to_close()
|
217
|
+
vec_title.set_legend_text(_("Title of the report"))
|
218
|
+
vec_title.set_legend_position_to_centroid()
|
219
|
+
vec_title.myprop.legendvisible = True
|
220
|
+
vec_title.find_minmax()
|
221
|
+
elts.add_vector(vec_title, forceparent=True)
|
222
|
+
|
223
|
+
vec_versions = vector(name=_("Versions"))
|
224
|
+
y_from_top = PAGE_HEIGHT - self.top_bottom_margin - HEIGHT_TITLE - self.padding
|
225
|
+
vec_versions.add_vertex(wv(self.left_right_margin, y_from_top))
|
226
|
+
vec_versions.add_vertex(wv(self.left_right_margin + WIDTH_VERSIONS, y_from_top))
|
227
|
+
vec_versions.add_vertex(wv(self.left_right_margin + WIDTH_VERSIONS, y_from_top - HEIGHT_VERSIONS))
|
228
|
+
vec_versions.add_vertex(wv(self.left_right_margin, y_from_top - HEIGHT_VERSIONS))
|
229
|
+
vec_versions.force_to_close()
|
230
|
+
vec_versions.set_legend_text(_("Versions of the software"))
|
231
|
+
vec_versions.set_legend_position_to_centroid()
|
232
|
+
vec_versions.myprop.legendvisible = True
|
233
|
+
vec_versions.find_minmax()
|
234
|
+
elts.add_vector(vec_versions, forceparent=True)
|
235
|
+
|
236
|
+
vec_logo = vector(name=_("Logo"))
|
237
|
+
# Logo is placed at the top right corner, after the title and versions
|
238
|
+
# Adjust the position based on the logo size
|
239
|
+
y_from_top = PAGE_HEIGHT - self.top_bottom_margin
|
240
|
+
vec_logo.add_vertex(wv(X_LOGO, y_from_top))
|
241
|
+
vec_logo.add_vertex(wv(X_LOGO + WIDTH_LOGO, y_from_top))
|
242
|
+
vec_logo.add_vertex(wv(X_LOGO + WIDTH_LOGO, y_from_top - HEIGHT_LOGO))
|
243
|
+
vec_logo.add_vertex(wv(X_LOGO, y_from_top - HEIGHT_LOGO))
|
244
|
+
vec_logo.force_to_close()
|
245
|
+
vec_logo.set_legend_text(_("Logo"))
|
246
|
+
vec_logo.set_legend_position_to_centroid()
|
247
|
+
vec_logo.myprop.legendvisible = True
|
248
|
+
vec_logo.find_minmax()
|
249
|
+
elts.add_vector(vec_logo, forceparent=True)
|
250
|
+
|
251
|
+
vec_footer = vector(name=_("Footer"))
|
252
|
+
vec_footer.add_vertex(wv(self.left_right_margin, self.top_bottom_margin))
|
253
|
+
vec_footer.add_vertex(wv(PAGE_WIDTH - self.left_right_margin, self.top_bottom_margin))
|
254
|
+
vec_footer.add_vertex(wv(PAGE_WIDTH - self.left_right_margin, self.top_bottom_margin + HEIGHT_FOOTER))
|
255
|
+
vec_footer.add_vertex(wv(self.left_right_margin, self.top_bottom_margin + HEIGHT_FOOTER))
|
256
|
+
vec_footer.force_to_close()
|
257
|
+
vec_footer.set_legend_text(_("Footer of the report"))
|
258
|
+
vec_footer.set_legend_position_to_centroid()
|
259
|
+
vec_footer.myprop.legendvisible = True
|
260
|
+
vec_footer.find_minmax()
|
261
|
+
elts.add_vector(vec_footer, forceparent=True)
|
262
|
+
|
263
|
+
self._layout = {}
|
264
|
+
self._doc = None # Placeholder for the PDF document
|
265
|
+
self._pdf_path = None # Placeholder for the PDF file path
|
266
|
+
|
267
|
+
def add_element(self, name:str, width:float, height:float, x:float = 0, y:float = 0) -> vector:
|
268
|
+
"""
|
269
|
+
Add an element to the layout.
|
270
|
+
"""
|
271
|
+
vec = vector(name=name)
|
272
|
+
vec.add_vertex(wv(x, y))
|
273
|
+
vec.add_vertex(wv(x + width, y))
|
274
|
+
vec.add_vertex(wv(x + width, y + height))
|
275
|
+
vec.add_vertex(wv(x, y + height))
|
276
|
+
vec.force_to_close()
|
277
|
+
vec.find_minmax()
|
278
|
+
vec.set_legend_text(name)
|
279
|
+
vec.set_legend_position_to_centroid()
|
280
|
+
vec.myprop.legendvisible = True
|
281
|
+
|
282
|
+
self['Elements'].add_vector(vec, forceparent=True)
|
283
|
+
|
284
|
+
return vec
|
285
|
+
|
286
|
+
def add_element_repeated(self, name:str, width:float, height:float,
|
287
|
+
first_x:float = 0, first_y:float = 0,
|
288
|
+
count_x:int = 1, count_y:int = 1,
|
289
|
+
padding:float = None) -> zone:
|
290
|
+
|
291
|
+
if padding is None:
|
292
|
+
padding = self.padding
|
293
|
+
|
294
|
+
delta_x = width + padding if count_x > 0 else -(padding + width)
|
295
|
+
delta_y = height + padding if count_y > 0 else -(padding + height)
|
296
|
+
|
297
|
+
count_x = abs(count_x)
|
298
|
+
count_y = abs(count_y)
|
299
|
+
|
300
|
+
x = first_x
|
301
|
+
y = first_y if delta_y > 0 else first_y - height
|
302
|
+
|
303
|
+
elements = zone(name=name + '_elements')
|
304
|
+
for j in range(count_y):
|
305
|
+
for i in range(count_x):
|
306
|
+
elements.add_vector(self.add_element(name + f"_{i}-{j}", width, height, x, y), forceparent=False)
|
307
|
+
x += delta_x
|
308
|
+
x = first_x
|
309
|
+
y += delta_y
|
310
|
+
|
311
|
+
elements.find_minmax()
|
312
|
+
|
313
|
+
return elements
|
314
|
+
|
315
|
+
|
316
|
+
def check_if_overlap(self, vec:vector) -> bool:
|
317
|
+
"""
|
318
|
+
Check if the vector overlaps with any existing vector in the layout.
|
319
|
+
"""
|
320
|
+
for existing_vec in self['Elements'].myvectors:
|
321
|
+
if vec.linestring.overlaps(existing_vec.linestring):
|
322
|
+
return True
|
323
|
+
return False
|
324
|
+
|
325
|
+
@property
|
326
|
+
def useful_part(self) -> vector:
|
327
|
+
"""
|
328
|
+
Get the useful part of the page, excluding margins.
|
329
|
+
"""
|
330
|
+
vec = self[('Page', _('Page'))]
|
331
|
+
vec.find_minmax()
|
332
|
+
|
333
|
+
version = self[('Elements', _('Versions'))]
|
334
|
+
version.find_minmax()
|
335
|
+
|
336
|
+
footer = self[('Elements', _('Footer'))]
|
337
|
+
footer.find_minmax()
|
338
|
+
|
339
|
+
useful_part = vector(name=_("Useful part of the page"))
|
340
|
+
useful_part.add_vertex(wv(vec.xmin + self.left_right_margin, version.ymin - self.padding))
|
341
|
+
useful_part.add_vertex(wv(vec.xmax - self.left_right_margin, version.ymin - self.padding))
|
342
|
+
useful_part.add_vertex(wv(vec.xmax - self.left_right_margin, footer.ymax + self.padding))
|
343
|
+
useful_part.add_vertex(wv(vec.xmin + self.left_right_margin, footer.ymax + self.padding))
|
344
|
+
useful_part.force_to_close()
|
345
|
+
|
346
|
+
useful_part.find_minmax()
|
347
|
+
useful_part.set_legend_text(_("Useful part of the page"))
|
348
|
+
useful_part.set_legend_position_to_centroid()
|
349
|
+
useful_part.myprop.legendvisible = True
|
350
|
+
|
351
|
+
return useful_part
|
352
|
+
|
353
|
+
@property
|
354
|
+
def page_dimension(self) -> tuple[float, float]:
|
355
|
+
"""
|
356
|
+
Get the dimensions of the page in centimeters.
|
357
|
+
"""
|
358
|
+
vec = self[('Page', _('Page'))]
|
359
|
+
vec.find_minmax()
|
360
|
+
width = vec.xmax - vec.xmin
|
361
|
+
height = vec.ymax - vec.ymin
|
362
|
+
return width, height
|
363
|
+
|
364
|
+
@property
|
365
|
+
def keys(self) -> list[str]:
|
366
|
+
"""
|
367
|
+
Get the keys of the layout.
|
368
|
+
"""
|
369
|
+
return [vec.myname for vec in self['Elements'].myvectors]
|
370
|
+
|
371
|
+
def to_dict(self):
|
372
|
+
"""
|
373
|
+
Convert the layout Zones to a dictionary.
|
374
|
+
"""
|
375
|
+
layout = {}
|
376
|
+
|
377
|
+
for vec in self['Elements'].myvectors:
|
378
|
+
vec.find_minmax()
|
379
|
+
layout[vec.myname] = rect_cm(vec.xmin, PAGE_HEIGHT - vec.ymax, vec.xmax - vec.xmin, vec.ymax - vec.ymin)
|
380
|
+
|
381
|
+
def plot(self, scale=1.):
|
382
|
+
"""
|
383
|
+
Plot the layout using matplotlib.
|
384
|
+
:param scale: Scale factor for the plot.
|
385
|
+
"""
|
386
|
+
fig, ax = plt.subplots(figsize=(cm2inches(PAGE_WIDTH) * scale, cm2inches(PAGE_HEIGHT)*scale))
|
387
|
+
|
388
|
+
self['Elements'].plot_matplotlib(ax = ax)
|
389
|
+
ax.set_aspect('equal')
|
390
|
+
|
391
|
+
ax.set_xlim(0, PAGE_WIDTH)
|
392
|
+
ax.set_ylim(0, PAGE_HEIGHT)
|
393
|
+
|
394
|
+
ax.set_yticks(list(np.arange(0, PAGE_HEIGHT, 1))+[PAGE_HEIGHT])
|
395
|
+
ax.set_xticks(list(np.arange(0, PAGE_WIDTH + 1, 1)))
|
396
|
+
|
397
|
+
plt.title(_("Layout of the report"))
|
398
|
+
plt.xlabel(_("Width (cm)"))
|
399
|
+
plt.ylabel(_("Height (cm)"))
|
400
|
+
# plt.grid(True)
|
401
|
+
|
402
|
+
return fig, ax
|
403
|
+
|
404
|
+
def _create_layout_pdf(self) -> dict:
|
405
|
+
"""
|
406
|
+
Create the layout dictionary for the report.
|
407
|
+
:return: A dictionary with layout information.
|
408
|
+
"""
|
409
|
+
for vec in self['Elements'].myvectors:
|
410
|
+
vec.find_minmax()
|
411
|
+
self._layout[vec.myname] = rect_cm(vec.xmin, PAGE_HEIGHT - vec.ymax, vec.xmax - vec.xmin, vec.ymax - vec.ymin)
|
412
|
+
|
413
|
+
return self._layout
|
414
|
+
|
415
|
+
def _summary_versions(self):
|
416
|
+
""" Find the versions of the simulation, wolfhece and the wolfgpu package """
|
417
|
+
import json
|
418
|
+
|
419
|
+
group_title = "Versions"
|
420
|
+
text = [f"Wolfhece : {wolfhece_version}",
|
421
|
+
f"Wolfgpu : {wolfgpu_version}",
|
422
|
+
f"Python : {sys.version.split()[0]}",
|
423
|
+
f"Operating System: {os.name}"
|
424
|
+
]
|
425
|
+
|
426
|
+
return group_title, text
|
427
|
+
|
428
|
+
def _insert_to_page(self, page: pdf.Page):
|
429
|
+
|
430
|
+
layout = self._create_layout_pdf()
|
431
|
+
|
432
|
+
page.insert_htmlbox(layout['Title'], f"<h1>{self.title}</h1>",
|
433
|
+
css='h1 {font-size:16pt; font-family:Helvetica; color:#333}')
|
434
|
+
|
435
|
+
# versions box
|
436
|
+
try:
|
437
|
+
text = self._summary_versions()
|
438
|
+
html, css = list_to_html_aligned(text[1], font_size="10pt", font_family="Helvetica")
|
439
|
+
spare_height, scale = page.insert_htmlbox(layout['Versions'], html, css=css, scale_low = 0.1)
|
440
|
+
|
441
|
+
if spare_height < 0.:
|
442
|
+
logging.warning("Text overflow in versions box. Adjusting scale.")
|
443
|
+
except:
|
444
|
+
logging.error("Failed to insert versions text. Using fallback method.")
|
445
|
+
|
446
|
+
rect = layout['Logo']
|
447
|
+
# Add the logo to the top-right corner
|
448
|
+
logo_path = Path(__file__).parent / 'wolf_report.png'
|
449
|
+
if logo_path.exists():
|
450
|
+
page.insert_image(rect, filename=str(logo_path),
|
451
|
+
keep_proportion=True,
|
452
|
+
overlay=True)
|
453
|
+
|
454
|
+
# Footer
|
455
|
+
# ------
|
456
|
+
# Insert the date and time of the report generation, the user and the PC name
|
457
|
+
footer_rect = layout['Footer']
|
458
|
+
footer_text = f"<p>Report generated on {dt.now()} by {os.getlogin()} on {platform.uname().node} - {platform.uname().machine} - {platform.uname().release} - {platform.uname().version}</br> \
|
459
|
+
This report does not guarantee the quality of the model and in no way commits the software developers.</p>"
|
460
|
+
page.insert_htmlbox(footer_rect, footer_text,
|
461
|
+
css='p {font-size:10pt; font-family:Helvetica; color:#BEBEBE; align-text:center}',)
|
462
|
+
|
463
|
+
def create_report(self) -> pdf.Document:
|
464
|
+
""" Create the PDF report for the default LayoutA4. """
|
465
|
+
|
466
|
+
# Create a new PDF document
|
467
|
+
self._doc = pdf.Document()
|
468
|
+
|
469
|
+
# Add a page
|
470
|
+
self._page = self._doc.new_page()
|
471
|
+
|
472
|
+
# Insert the layout into the page
|
473
|
+
self._insert_to_page(self._page)
|
474
|
+
|
475
|
+
return self._doc
|
476
|
+
|
477
|
+
def save_report(self, output_path: Path | str):
|
478
|
+
""" Save the report to a PDF file """
|
479
|
+
|
480
|
+
if self._doc is None:
|
481
|
+
self.create_report()
|
482
|
+
|
483
|
+
try:
|
484
|
+
self._doc.subset_fonts()
|
485
|
+
self._doc.save(output_path, garbage=3, deflate=True)
|
486
|
+
self._pdf_path = output_path
|
487
|
+
except Exception as e:
|
488
|
+
logging.error(f"Failed to save the report to {output_path}: {e}")
|
489
|
+
logging.error("Please check if the file is already opened.")
|
490
|
+
self._pdf_path = None
|
491
|
+
return
|
492
|
+
|
493
|
+
@property
|
494
|
+
def pdf_path(self):
|
495
|
+
""" Return the PDF document """
|
496
|
+
return self._pdf_path
|