Arcane  4.1.12.0
User documentation
Loading...
Searching...
No Matches
SimpleHTMLMeshAMRPatchExporter.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* SimpleHTMLMeshAMRPatchExporter.cc (C) 2000-2026 */
9/* */
10/* HTML mesh writer, with SVG. */
11/*---------------------------------------------------------------------------*/
12/*---------------------------------------------------------------------------*/
13
14// This must be put first for MSVC otherwise we won't have 'M_PI'
15#define _USE_MATH_DEFINES
16#include <cmath>
17
18#include "arcane/cartesianmesh/SimpleHTMLMeshAMRPatchExporter.h"
19
20#include "arcane/cartesianmesh/AMRPatchPosition.h"
21
22#include "arcane/utils/Iostream.h"
23#include "arcane/utils/StringBuilder.h"
24
25#include "arcane/core/ItemGroup.h"
26#include "arcane/core/IMesh.h"
27#include "arcane/core/VariableTypes.h"
28
29#include <set>
30#include <map>
31
32/*---------------------------------------------------------------------------*/
33/*---------------------------------------------------------------------------*/
34
35namespace Arcane
36{
37using std::ostream;
38
39/*---------------------------------------------------------------------------*/
40/*---------------------------------------------------------------------------*/
41
43{
44 public:
45
46 Impl() = default;
47 void addPatch(const CartesianPatch& patch);
48 void write(ostream& ofile);
49
50 private:
51
52 void _writeJs(ostream& ofile);
53 void _writeCss(ostream& ofile);
54 void _writeBodyHtml(ostream& ofile);
55 void _writeHeaderSvg(const CartesianPatch& ground_patch);
56 void _writeText(Real x, Real y, StringView color, StringView text, Real rotation, bool do_background);
57 void _writePatch(const CartesianPatch& patch);
58
59 private:
60
61 Real m_font_size = 0;
62 bool m_has_ground = false;
63 bool m_has_patch = false;
64 StringBuilder m_header_svg{};
65 StringBuilder m_patches{};
66};
67
68/*---------------------------------------------------------------------------*/
69/*---------------------------------------------------------------------------*/
70
71/*---------------------------------------------------------------------------*/
72/*---------------------------------------------------------------------------*/
73
74void SimpleHTMLMeshAMRPatchExporter::Impl::
75addPatch(const CartesianPatch& patch)
76{
77 if (patch.position().isNull()) {
78 return;
79 }
80 if (patch.level() == 0) {
81 if (m_has_ground) {
82 ARCANE_FATAL("Ground patch already wrote");
83 }
84 _writeHeaderSvg(patch);
85 m_has_ground = true;
86 }
87 Int32 level = patch.level();
88 Int32 index = patch.index();
89
90 m_patches += String::format("<g class='level-{0}' id='patch-{1}'>\n", level, index);
91 _writePatch(patch);
92 m_patches += "</g>\n";
93 m_has_patch = true;
94}
95
96/*---------------------------------------------------------------------------*/
97/*---------------------------------------------------------------------------*/
98
99void SimpleHTMLMeshAMRPatchExporter::Impl::
100write(ostream& ofile)
101{
102 if (!m_has_patch) {
103 return;
104 }
105 if (!m_has_ground) {
106 ARCANE_FATAL("Need ground patch to write");
107 }
108 ofile << "<!DOCTYPE html>\n";
109 ofile << "<html>\n";
110
111 ofile << "<head>\n";
112 _writeJs(ofile);
113 _writeCss(ofile);
114 ofile << "</head>\n";
115
116 ofile << "<body>\n";
117 _writeBodyHtml(ofile);
118 ofile << "</body>\n";
119
120 ofile << "</html>\n";
121}
122
123/*---------------------------------------------------------------------------*/
124/*---------------------------------------------------------------------------*/
125
126void SimpleHTMLMeshAMRPatchExporter::Impl::
127_writeJs(ostream& ofile)
128{
129 ofile << "<script>\n";
130 //TODO
131 ofile << "</script>\n";
132}
133
134/*---------------------------------------------------------------------------*/
135/*---------------------------------------------------------------------------*/
136
137void SimpleHTMLMeshAMRPatchExporter::Impl::
138_writeCss(ostream& ofile)
139{
140 ofile << "<style>\n";
141
142 ofile << ".uid-text {font-size: " << m_font_size << "px;}\n";
143
144 ofile << "</style>\n";
145}
146
147/*---------------------------------------------------------------------------*/
148/*---------------------------------------------------------------------------*/
149
150void SimpleHTMLMeshAMRPatchExporter::Impl::
151_writeBodyHtml(ostream& ofile)
152{
153 ofile << m_header_svg;
154 ofile << m_patches;
155 ofile << "</g>\n";
156 ofile << "</svg>\n";
157}
158
159/*---------------------------------------------------------------------------*/
160/*---------------------------------------------------------------------------*/
161
162void SimpleHTMLMeshAMRPatchExporter::Impl::
163_writeHeaderSvg(const CartesianPatch& ground_patch)
164{
165 CellGroup cells = ground_patch.patchInterface()->cells();
166 if (ground_patch.level() != 0) {
167 ARCANE_FATAL("Not ground patch");
168 }
169 if (cells.null()) {
170 ARCANE_FATAL("Empty patch");
171 }
172 IMesh* mesh = cells.mesh();
173 Int32 mesh_dim = mesh->dimension();
174 if (mesh_dim != 2)
175 ARCANE_FATAL("Invalid dimension ({0}) for mesh. Only 2D mesh is allowed", mesh_dim);
176
177 // Note: since by default in SVG the origin is top left, we take for each
178 // 'Y' value its opposite for display.
179 // NOTE: we could use SVG transformations but it is more complicated to
180 // handle for text display
181
182 VariableNodeReal3& nodes_coord = mesh->nodesCoordinates();
183 Real mul_value = 1000.0;
184 const Real min_val = std::numeric_limits<Real>::lowest();
185 const Real max_val = std::numeric_limits<Real>::max();
186 Real2 min_bbox(max_val, max_val);
187 Real2 max_bbox(min_val, min_val);
188 // Calculate the center of the cells and the bounding box of the cell group.
189 std::map<Int32, Real2> cells_center;
190 ENUMERATE_CELL (icell, cells) {
191 Cell cell = *icell;
192 Real3 center;
193 Integer nb_node = cell.nbNode();
194 if (nb_node > 0) {
195 for (Integer i = 0; i < nb_node; ++i) {
196 Real3 node_coord_3d = nodes_coord[cell.node(i)] * mul_value;
197 Real2 node_coord(node_coord_3d.x, -node_coord_3d.y);
198 min_bbox = math::min(min_bbox, node_coord);
199 max_bbox = math::max(max_bbox, node_coord);
200 center += node_coord_3d;
201 }
202 center /= nb_node;
203 }
204 Real2 center_2d(center.x, -center.y);
205 cells_center[cell.localId()] = center_2d;
206 }
207
208 Real bbox_width = math::abs(max_bbox.x - min_bbox.x);
209 Real bbox_height = math::abs(max_bbox.y - min_bbox.y);
210 Real max_dim = math::max(bbox_width, bbox_height);
211 m_font_size = max_dim / 80.0;
212
213 // Add 10% of the patch dimensions on both sides of the viewBox to ensure
214 // that the text is properly written (as it might overflow the bounding box)
215 m_header_svg += "<?xml version=\"1.0\"?>\n";
216 m_header_svg += String::format("<svg viewBox='{0},{1},{2},{3}' xmlns='http://www.w3.org/2000/svg' version='1.1'>\n",
217 (min_bbox.x - bbox_width * 0.1),
218 (min_bbox.y - bbox_height * 0.1),
219 (bbox_width * 1.2),
220 (bbox_height * 1.2));
221 m_header_svg += String::format("<!-- V3 bbox min_x={0} min_y={1} max_x={2} max_y={3} -->",
222 min_bbox.x,
223 min_bbox.y,
224 max_bbox.x,
225 max_bbox.y);
226
227 m_header_svg += "<title>Mesh</title>\n";
228 m_header_svg += "<desc>MeshExample</desc>\n";
229
230 //ofile << "<g transform='matrix(1,0,0,-1,0,200)'>\n";
231 //ofile << "<g transform='translate(" << min_bbox.x << "," << -min_bbox.y << ")'>\n";
232 m_header_svg += "<g>\n";
233}
234
235/*---------------------------------------------------------------------------*/
236/*---------------------------------------------------------------------------*/
237
238void SimpleHTMLMeshAMRPatchExporter::Impl::
239_writeText(Real x, Real y, StringView color, StringView text, Real rotation, bool do_background)
240{
241 // Displays a white background below the text.
242 if (do_background) {
243 m_patches += String::format("<text class='uid-text' x='{0}' y='{1}' dominant-baseline='central' text-anchor='middle' style='stroke:white; stroke-width:0.6em'", x, y);
244 if (rotation != 0.0) {
245 m_patches += String::format(" transform='rotate({0}, {1}, {2})'", rotation, x, y);
246 }
247 m_patches += String::format(">{0}</text>\n", text);
248 }
249
250 m_patches += String::format("<text class='uid-text' x='{0}' y='{1}' dominant-baseline='central' text-anchor='middle' fill='{2}'", x, y, color);
251
252 if (rotation != 0.0) {
253 m_patches += String::format(" transform='rotate({0}, {1}, {2})'", rotation, x, y);
254 }
255 m_patches += String::format(">{0}</text>\n", text);
256}
257
258/*---------------------------------------------------------------------------*/
259/*---------------------------------------------------------------------------*/
260
261void SimpleHTMLMeshAMRPatchExporter::Impl::
262_writePatch(const CartesianPatch& patch)
263{
264 CellGroup cells = patch.patchInterface()->cells();
265 if (cells.null())
266 return;
267 IMesh* mesh = cells.mesh();
268 Int32 mesh_dim = mesh->dimension();
269 if (mesh_dim != 2)
270 ARCANE_FATAL("Invalid dimension ({0}) for mesh. Only 2D mesh is allowed", mesh_dim);
271
272 // Note: since by default in SVG the origin is top left, we take for each
273 // 'Y' value its opposite for display.
274 // NOTE: we could use SVG transformations but it is more complicated to
275 // handle for text display
276
277 VariableNodeReal3& nodes_coord = mesh->nodesCoordinates();
278 Real mul_value = 1000.0;
279 const Real min_val = std::numeric_limits<Real>::lowest();
280 const Real max_val = std::numeric_limits<Real>::max();
281 Real2 min_bbox(max_val, max_val);
282 Real2 max_bbox(min_val, min_val);
283
284 // Calculate the center of the cells and the bounding box of the cell group.
285 std::map<Int32, Real2> cells_center;
286 ENUMERATE_CELL (icell, cells) {
287 Cell cell = *icell;
288 Real3 center;
289 Integer nb_node = cell.nbNode();
290 if (nb_node > 0) {
291 for (Integer i = 0; i < nb_node; ++i) {
292 Real3 node_coord_3d = nodes_coord[cell.node(i)] * mul_value;
293 Real2 node_coord(node_coord_3d.x, -node_coord_3d.y);
294 min_bbox = math::min(min_bbox, node_coord);
295 max_bbox = math::max(max_bbox, node_coord);
296 center += node_coord_3d;
297 }
298 center /= nb_node;
299 }
300 Real2 center_2d(center.x, -center.y);
301 cells_center[cell.localId()] = center_2d;
302 }
303
304 // Display the contour and uniqueId() for each cell.
305 ENUMERATE_CELL (icell, cells) {
306 Cell cell = *icell;
307 Real2 cell_pos = cells_center[cell.localId()];
308 Integer nb_node = cell.nbNode();
309 m_patches += "<path d='";
310 nb_node = cell.typeInfo()->linearTypeInfo()->nbLocalNode();
311 for (Integer i = 0; i < nb_node; ++i) {
312 Real3 node_coord_3d = nodes_coord[cell.node(i)];
313 Real2 node_coord(node_coord_3d.x, -node_coord_3d.y);
314 node_coord *= mul_value;
315 if (i == 0)
316 m_patches += "M ";
317 else
318 m_patches += "L ";
319 // performs a homothety to clearly see the faces in case of welding.
320 Real2 coord = cell_pos + (node_coord - cell_pos) * 0.98;
321 m_patches += String::format("{0} {1} ", coord.x, coord.y);
322 }
323 m_patches += "z'";
324 if (icell->hasFlags(ItemFlags::II_Overlap) && icell->hasFlags(ItemFlags::II_InPatch)) {
325 if (cell.isOwn())
326 m_patches += " fill='magenta'";
327 else
328 m_patches += " fill='darkmagenta'";
329 }
330 if (icell->hasFlags(ItemFlags::II_Overlap)) {
331 if (cell.isOwn())
332 m_patches += " fill='orange'";
333 else
334 m_patches += " fill='darkorange'";
335 }
336 else {
337 if (cell.isOwn())
338 m_patches += " fill='yellow'";
339 else
340 m_patches += " fill='gold'";
341 }
342 m_patches += " stroke='black'";
343 m_patches += " stroke-width='1'/>\n";
344 _writeText(cell_pos.x, cell_pos.y, "blue", String::fromNumber(cell.uniqueId().asInt64()), 0.0, false);
345 }
346
347 // Display the uniqueId() for each node.
348 {
349 // Set of nodes already processed to display them only once.
350 std::set<Int32> nodes_done;
351 ENUMERATE_CELL (icell, cells) {
352 Cell cell = *icell;
353 Integer nb_node = cell.nbNode();
354 for (Integer i = 0; i < nb_node; ++i) {
355 Node node = cell.node(i);
356 Int32 lid = node.localId();
357 if (nodes_done.find(lid) != nodes_done.end())
358 continue;
359 nodes_done.insert(lid);
360 Real3 coord_3d = nodes_coord[node];
361 Real2 coord(coord_3d.x, -coord_3d.y);
362 coord *= mul_value;
363 _writeText(coord.x, coord.y, "green", String::fromNumber(node.uniqueId().asInt64()), 0.0, true);
364 }
365 }
366 }
367
368 // Display the uniqueId() for each face.
369 // Performs a possible rotation so that the display of the face number is aligned
370 // with its segment.
371 {
372 // Set of faces already processed to display them only once.
373 std::set<Int32> faces_done;
374 ENUMERATE_CELL (icell, cells) {
375 Cell cell = *icell;
376 Integer nb_face = cell.nbFace();
377 for (Integer i = 0; i < nb_face; ++i) {
378 Face face = cell.face(i);
379 Int32 lid = face.localId();
380 if (faces_done.find(lid) != faces_done.end())
381 continue;
382 faces_done.insert(lid);
383 // In case of multi-dimensional mesh, it is possible
384 // to have faces reduced to a point.
385 if (face.nbNode() < 2)
386 continue;
387 Real3 node0_coord = nodes_coord[face.node(0)];
388 Real3 node1_coord = nodes_coord[face.node(1)];
389 Real3 face_coord_3d = (node0_coord + node1_coord) / 2.0;
390
391 Real2 face_coord(face_coord_3d.x, -face_coord_3d.y);
392 face_coord *= mul_value;
393 Real3 direction = node1_coord - node0_coord;
394 direction = math::mutableNormalize(direction);
395 // TODO: check between -1.0 and 1.0
396 // Calculates the rotation angle so that the display of the face number is aligned with
397 // the edge line of the face.
398 double angle = math::abs(std::asin(direction.y)) / M_PI * 180.0;
399 Real2 cell_center = cells_center[cell.localId()];
400 Real2 coord = cell_center + (face_coord - cell_center) * 0.92;
401 _writeText(coord.x, coord.y, "red", String::fromNumber(face.uniqueId().asInt64()), angle, true);
402 }
403 }
404 }
405}
406
407/*---------------------------------------------------------------------------*/
408/*---------------------------------------------------------------------------*/
409
410/*---------------------------------------------------------------------------*/
411/*---------------------------------------------------------------------------*/
412
413SimpleHTMLMeshAMRPatchExporter::
414SimpleHTMLMeshAMRPatchExporter()
415: m_p(new Impl())
416{}
417
418/*---------------------------------------------------------------------------*/
419/*---------------------------------------------------------------------------*/
420
421SimpleHTMLMeshAMRPatchExporter::
422~SimpleHTMLMeshAMRPatchExporter()
423{
424 delete m_p;
425}
426
427/*---------------------------------------------------------------------------*/
428/*---------------------------------------------------------------------------*/
429
430void SimpleHTMLMeshAMRPatchExporter::
431addPatch(const CartesianPatch& patch)
432{
433 m_p->addPatch(patch);
434}
435
436/*---------------------------------------------------------------------------*/
437/*---------------------------------------------------------------------------*/
438
439void SimpleHTMLMeshAMRPatchExporter::
440write(std::ostream& ofile)
441{
442 m_p->write(ofile);
443}
444
445/*---------------------------------------------------------------------------*/
446/*---------------------------------------------------------------------------*/
447
448} // End namespace Arcane
449
450/*---------------------------------------------------------------------------*/
451/*---------------------------------------------------------------------------*/
#define ARCANE_FATAL(...)
Macro throwing a FatalErrorException.
#define ENUMERATE_CELL(name, group)
Generic enumerator for a cell group.
bool isNull() const
Method to know if the patch is null.
AMR Patch of a Cartesian mesh.
Integer index() const
Index of the patch in the patch array.
AMRPatchPosition position() const
Method to retrieve the patch position in the Cartesian mesh.
Integer level() const
Patch level.
virtual Integer dimension()=0
Mesh dimension (1D, 2D, or 3D).
@ II_Overlap
[AMR Patch] The entity is marked as overlapping with at least one AMR patch.
Definition ItemFlags.h:87
@ II_InPatch
[AMR Patch] The entity is marked as being in an AMR patch.
Definition ItemFlags.h:92
IMesh * mesh() const
Mesh to which this group belongs (0 for the null group).
Definition ItemGroup.h:131
Unicode character string constructor.
View of a UTF-8 character string.
Definition StringView.h:44
__host__ __device__ Real2 min(Real2 a, Real2 b)
Returns the minimum of two Real2.
Definition MathUtils.h:346
T max(const T &a, const T &b, const T &c)
Returns the maximum of three elements.
Definition MathUtils.h:407
ItemGroupT< Cell > CellGroup
Group of cells.
Definition ItemTypes.h:184
MeshVariableScalarRefT< Node, Real3 > VariableNodeReal3
Coordinate type quantity at node.
Real2 & mutableNormalize(Real2 &v)
Normalizes the pair.
Definition Real2.h:494
-- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature --
Int32 Integer
Type representing an integer.
double Real
Type representing a real number.
@ Cell
The mesh is AMR by cell.
Definition MeshKind.h:53
std::int32_t Int32
Signed integer type of 32 bits.