1#[allow(unused_imports)]
6use super::functions::*;
7use crate::Result;
8use oxiphysics_core::math::Vec3;
9use std::fs::File;
10use std::io::{BufWriter, Write};
11use std::path::Path;
12
13pub struct VtuGrid {
19 pub points: Vec<[f64; 3]>,
21 pub cells: Vec<Vec<usize>>,
23 pub cell_types: Vec<VtkCellType>,
25 pub point_data: Vec<VtkDataArray>,
27 pub cell_data: Vec<VtkDataArray>,
29}
30impl VtuGrid {
31 pub fn new() -> Self {
33 Self {
34 points: Vec::new(),
35 cells: Vec::new(),
36 cell_types: Vec::new(),
37 point_data: Vec::new(),
38 cell_data: Vec::new(),
39 }
40 }
41 pub fn add_point(&mut self, p: [f64; 3]) -> usize {
43 let idx = self.points.len();
44 self.points.push(p);
45 idx
46 }
47 pub fn add_cell(&mut self, connectivity: Vec<usize>, cell_type: VtkCellType) {
49 self.cells.push(connectivity);
50 self.cell_types.push(cell_type);
51 }
52 pub fn add_point_scalar(&mut self, name: &str, values: Vec<f64>) {
54 self.point_data.push(VtkDataArray::Scalar {
55 name: name.to_owned(),
56 values,
57 });
58 }
59 pub fn add_point_vector(&mut self, name: &str, values: Vec<[f64; 3]>) {
61 self.point_data.push(VtkDataArray::Vector3 {
62 name: name.to_owned(),
63 values,
64 });
65 }
66 pub fn add_cell_scalar(&mut self, name: &str, values: Vec<f64>) {
68 self.cell_data.push(VtkDataArray::Scalar {
69 name: name.to_owned(),
70 values,
71 });
72 }
73 pub fn n_points(&self) -> usize {
75 self.points.len()
76 }
77 pub fn n_cells(&self) -> usize {
79 self.cells.len()
80 }
81 pub fn to_vtu_string(&self) -> String {
85 let mut s = String::new();
86 s.push_str("<?xml version=\"1.0\"?>\n");
87 s.push_str(
88 "<VTKFile type=\"UnstructuredGrid\" version=\"0.1\" byte_order=\"LittleEndian\">\n",
89 );
90 s.push_str(" <UnstructuredGrid>\n");
91 s.push_str(&format!(
92 " <Piece NumberOfPoints=\"{}\" NumberOfCells=\"{}\">\n",
93 self.n_points(),
94 self.n_cells()
95 ));
96 s.push_str(" <Points>\n");
97 s.push_str(
98 " <DataArray type=\"Float64\" NumberOfComponents=\"3\" format=\"ascii\">\n",
99 );
100 for p in &self.points {
101 s.push_str(&format!(" {} {} {}\n", p[0], p[1], p[2]));
102 }
103 s.push_str(" </DataArray>\n");
104 s.push_str(" </Points>\n");
105 s.push_str(" <Cells>\n");
106 s.push_str(" <DataArray type=\"Int64\" Name=\"connectivity\" format=\"ascii\">\n");
107 s.push_str(" ");
108 let mut first = true;
109 for conn in &self.cells {
110 for &idx in conn {
111 if !first {
112 s.push(' ');
113 }
114 s.push_str(&idx.to_string());
115 first = false;
116 }
117 }
118 s.push('\n');
119 s.push_str(" </DataArray>\n");
120 s.push_str(" <DataArray type=\"Int64\" Name=\"offsets\" format=\"ascii\">\n");
121 s.push_str(" ");
122 let mut offset: usize = 0;
123 for (i, conn) in self.cells.iter().enumerate() {
124 if i > 0 {
125 s.push(' ');
126 }
127 offset += conn.len();
128 s.push_str(&offset.to_string());
129 }
130 s.push('\n');
131 s.push_str(" </DataArray>\n");
132 s.push_str(" <DataArray type=\"UInt8\" Name=\"types\" format=\"ascii\">\n");
133 s.push_str(" ");
134 for (i, ct) in self.cell_types.iter().enumerate() {
135 if i > 0 {
136 s.push(' ');
137 }
138 s.push_str(&(*ct as u8).to_string());
139 }
140 s.push('\n');
141 s.push_str(" </DataArray>\n");
142 s.push_str(" </Cells>\n");
143 if !self.point_data.is_empty() {
144 s.push_str(" <PointData>\n");
145 for arr in &self.point_data {
146 s.push_str(&Self::data_array_xml(arr));
147 }
148 s.push_str(" </PointData>\n");
149 }
150 if !self.cell_data.is_empty() {
151 s.push_str(" <CellData>\n");
152 for arr in &self.cell_data {
153 s.push_str(&Self::data_array_xml(arr));
154 }
155 s.push_str(" </CellData>\n");
156 }
157 s.push_str(" </Piece>\n");
158 s.push_str(" </UnstructuredGrid>\n");
159 s.push_str("</VTKFile>\n");
160 s
161 }
162 fn data_array_xml(arr: &VtkDataArray) -> String {
164 let mut s = String::new();
165 match arr {
166 VtkDataArray::Scalar { name, values } => {
167 s.push_str(
168 &format!(
169 " <DataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"1\" format=\"ascii\">\n",
170 name
171 ),
172 );
173 s.push_str(" ");
174 for (i, v) in values.iter().enumerate() {
175 if i > 0 {
176 s.push(' ');
177 }
178 s.push_str(&v.to_string());
179 }
180 s.push('\n');
181 s.push_str(" </DataArray>\n");
182 }
183 VtkDataArray::Vector3 { name, values } => {
184 s.push_str(
185 &format!(
186 " <DataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"3\" format=\"ascii\">\n",
187 name
188 ),
189 );
190 for v in values {
191 s.push_str(&format!(" {} {} {}\n", v[0], v[1], v[2]));
192 }
193 s.push_str(" </DataArray>\n");
194 }
195 VtkDataArray::Integer { name, values } => {
196 s.push_str(
197 &format!(
198 " <DataArray type=\"Int64\" Name=\"{}\" NumberOfComponents=\"1\" format=\"ascii\">\n",
199 name
200 ),
201 );
202 s.push_str(" ");
203 for (i, v) in values.iter().enumerate() {
204 if i > 0 {
205 s.push(' ');
206 }
207 s.push_str(&v.to_string());
208 }
209 s.push('\n');
210 s.push_str(" </DataArray>\n");
211 }
212 }
213 s
214 }
215 pub fn write_pvd_collection(base_name: &str, time_steps: &[(f64, String)]) -> String {
224 let mut s = String::new();
225 s.push_str("<?xml version=\"1.0\"?>\n");
226 s.push_str(
227 &format!(
228 "<VTKFile type=\"Collection\" version=\"0.1\" byte_order=\"LittleEndian\">\n <!-- {} -->\n",
229 base_name
230 ),
231 );
232 s.push_str(" <Collection>\n");
233 for (time, filename) in time_steps {
234 s.push_str(&format!(
235 " <DataSet timestep=\"{}\" group=\"\" part=\"0\" file=\"{}\"/>\n",
236 time, filename
237 ));
238 }
239 s.push_str(" </Collection>\n");
240 s.push_str("</VTKFile>\n");
241 s
242 }
243 pub fn from_points(positions: &[[f64; 3]]) -> Self {
245 let mut grid = Self::new();
246 for &p in positions {
247 let idx = grid.add_point(p);
248 grid.add_cell(vec![idx], VtkCellType::Vertex);
249 }
250 grid
251 }
252 pub fn from_tet_mesh(nodes: &[[f64; 3]], elements: &[[usize; 4]]) -> Self {
257 let mut grid = Self::new();
258 for &n in nodes {
259 grid.add_point(n);
260 }
261 for &[a, b, c, d] in elements {
262 grid.add_cell(vec![a, b, c, d], VtkCellType::Tetra);
263 }
264 grid
265 }
266}
267#[allow(dead_code)]
269#[derive(Debug, Clone)]
270pub struct VtkFieldRecord {
271 pub name: String,
273 pub values: Vec<f64>,
275}
276impl VtkFieldRecord {
277 pub fn new(name: impl Into<String>, values: Vec<f64>) -> Self {
279 Self {
280 name: name.into(),
281 values,
282 }
283 }
284 pub fn len(&self) -> usize {
286 self.values.len()
287 }
288 pub fn is_empty(&self) -> bool {
290 self.values.is_empty()
291 }
292}
293#[derive(Debug, Clone, Copy)]
295pub enum VtkCellType {
296 Vertex = 1,
298 Line = 3,
300 Triangle = 5,
302 Quad = 9,
304 Tetra = 10,
306 Hexahedron = 12,
308 Wedge = 13,
310 Pyramid = 14,
312}
313#[allow(dead_code)]
315#[derive(Debug, Clone)]
316pub struct VtkLegacyData {
317 pub title: String,
319 pub dataset_type: String,
321 pub points: Vec<[f64; 3]>,
323 pub point_scalars: Vec<(String, Vec<f64>)>,
325}
326impl VtkLegacyData {
327 pub fn empty() -> Self {
329 Self {
330 title: String::new(),
331 dataset_type: String::new(),
332 points: Vec::new(),
333 point_scalars: Vec::new(),
334 }
335 }
336}
337#[allow(dead_code)]
342#[derive(Debug, Clone, Default)]
343pub struct VtkFieldData {
344 pub records: Vec<VtkFieldRecord>,
346}
347impl VtkFieldData {
348 pub fn new() -> Self {
350 Self {
351 records: Vec::new(),
352 }
353 }
354 pub fn add(&mut self, name: impl Into<String>, values: Vec<f64>) {
356 self.records.push(VtkFieldRecord::new(name, values));
357 }
358 pub fn add_scalar(&mut self, name: impl Into<String>, value: f64) {
360 self.add(name, vec![value]);
361 }
362 pub fn get(&self, name: &str) -> Option<&VtkFieldRecord> {
364 self.records.iter().find(|r| r.name == name)
365 }
366 pub fn to_vtk_field_string(&self) -> String {
368 if self.records.is_empty() {
369 return String::new();
370 }
371 let mut s = String::new();
372 s.push_str(&format!("FIELD FieldData {}\n", self.records.len()));
373 for rec in &self.records {
374 s.push_str(&format!("{} {} 1 float\n", rec.name, rec.values.len()));
375 let vals: Vec<String> = rec.values.iter().map(|v| v.to_string()).collect();
376 s.push_str(&vals.join(" "));
377 s.push('\n');
378 }
379 s
380 }
381}
382pub struct VtkWriter;
384impl VtkWriter {
385 pub fn write_points(path: &str, positions: &[Vec3]) -> Result<()> {
387 let file = File::create(Path::new(path))?;
388 let mut w = BufWriter::new(file);
389 writeln!(w, "# vtk DataFile Version 3.0")?;
390 writeln!(w, "OxiPhysics point cloud")?;
391 writeln!(w, "ASCII")?;
392 writeln!(w, "DATASET POLYDATA")?;
393 writeln!(w, "POINTS {} float", positions.len())?;
394 for p in positions {
395 writeln!(w, "{} {} {}", p.x, p.y, p.z)?;
396 }
397 w.flush()?;
398 Ok(())
399 }
400 #[allow(clippy::too_many_arguments)]
404 pub fn write_unstructured_grid(
405 path: &str,
406 positions: &[Vec3],
407 cells: &[[usize; 4]],
408 scalars: Option<(&str, &[f64])>,
409 vectors: Option<(&str, &[Vec3])>,
410 ) -> Result<()> {
411 let file = File::create(Path::new(path))?;
412 let mut w = BufWriter::new(file);
413 writeln!(w, "# vtk DataFile Version 3.0")?;
414 writeln!(w, "OxiPhysics unstructured grid")?;
415 writeln!(w, "ASCII")?;
416 writeln!(w, "DATASET UNSTRUCTURED_GRID")?;
417 writeln!(w, "POINTS {} float", positions.len())?;
418 for p in positions {
419 writeln!(w, "{} {} {}", p.x, p.y, p.z)?;
420 }
421 let ncells = cells.len();
422 let cell_size = ncells * 5;
423 writeln!(w, "CELLS {} {}", ncells, cell_size)?;
424 for c in cells {
425 writeln!(w, "4 {} {} {} {}", c[0], c[1], c[2], c[3])?;
426 }
427 writeln!(w, "CELL_TYPES {}", ncells)?;
428 for _ in 0..ncells {
429 writeln!(w, "10")?;
430 }
431 let has_data = scalars.is_some() || vectors.is_some();
432 if has_data {
433 writeln!(w, "POINT_DATA {}", positions.len())?;
434 }
435 if let Some((name, vals)) = scalars {
436 writeln!(w, "SCALARS {} float 1", name)?;
437 writeln!(w, "LOOKUP_TABLE default")?;
438 for v in vals {
439 writeln!(w, "{}", v)?;
440 }
441 }
442 if let Some((name, vecs)) = vectors {
443 writeln!(w, "VECTORS {} float", name)?;
444 for v in vecs {
445 writeln!(w, "{} {} {}", v.x, v.y, v.z)?;
446 }
447 }
448 w.flush()?;
449 Ok(())
450 }
451 pub fn write_polydata(path: &str, positions: &[Vec3], triangles: &[[usize; 3]]) -> Result<()> {
453 let file = File::create(Path::new(path))?;
454 let mut w = BufWriter::new(file);
455 writeln!(w, "# vtk DataFile Version 3.0")?;
456 writeln!(w, "OxiPhysics polydata")?;
457 writeln!(w, "ASCII")?;
458 writeln!(w, "DATASET POLYDATA")?;
459 writeln!(w, "POINTS {} float", positions.len())?;
460 for p in positions {
461 writeln!(w, "{} {} {}", p.x, p.y, p.z)?;
462 }
463 let ntri = triangles.len();
464 writeln!(w, "POLYGONS {} {}", ntri, ntri * 4)?;
465 for t in triangles {
466 writeln!(w, "3 {} {} {}", t[0], t[1], t[2])?;
467 }
468 w.flush()?;
469 Ok(())
470 }
471}
472#[allow(dead_code)]
474pub struct VtkTimeSeries {
475 pub times: Vec<f64>,
477 pub grids: Vec<VtuGrid>,
479 pub base_name: String,
481}
482impl VtkTimeSeries {
483 pub fn new(base_name: &str) -> Self {
485 Self {
486 times: Vec::new(),
487 grids: Vec::new(),
488 base_name: base_name.to_owned(),
489 }
490 }
491 pub fn push(&mut self, t: f64, grid: VtuGrid) {
493 self.times.push(t);
494 self.grids.push(grid);
495 }
496 pub fn n_steps(&self) -> usize {
498 self.times.len()
499 }
500 pub fn to_pvd_string(&self) -> String {
504 let entries: Vec<(f64, String)> = self
505 .times
506 .iter()
507 .enumerate()
508 .map(|(i, &t)| (t, format!("{}_{:05}.vtu", self.base_name, i)))
509 .collect();
510 VtuGrid::write_pvd_collection(&self.base_name, &entries)
511 }
512 pub fn vtu_string(&self, i: usize) -> Option<String> {
514 self.grids.get(i).map(|g| g.to_vtu_string())
515 }
516 pub fn estimated_size_bytes(&self) -> usize {
518 self.grids.iter().map(|g| g.n_points() * 30).sum()
519 }
520}
521#[allow(dead_code)]
523#[derive(Debug, Clone)]
524pub struct PvdEntry {
525 pub time: f64,
527 pub filename: String,
529 pub part: u32,
531 pub group: String,
533}
534impl PvdEntry {
535 pub fn new(time: f64, filename: impl Into<String>) -> Self {
537 Self {
538 time,
539 filename: filename.into(),
540 part: 0,
541 group: String::new(),
542 }
543 }
544}
545#[allow(dead_code)]
550pub struct VtkMultiBlock {
551 pub blocks: Vec<VtkBlock>,
553 pub title: String,
555}
556impl VtkMultiBlock {
557 pub fn new(title: impl Into<String>) -> Self {
559 Self {
560 blocks: Vec::new(),
561 title: title.into(),
562 }
563 }
564 pub fn add_block(&mut self, name: impl Into<String>, grid: VtuGrid) {
566 self.blocks.push(VtkBlock::new(name, grid));
567 }
568 pub fn n_blocks(&self) -> usize {
570 self.blocks.len()
571 }
572 pub fn total_points(&self) -> usize {
574 self.blocks.iter().map(|b| b.grid.n_points()).sum()
575 }
576 pub fn total_cells(&self) -> usize {
578 self.blocks.iter().map(|b| b.grid.n_cells()).sum()
579 }
580 pub fn to_vtm_string(&self) -> String {
584 let mut s = String::new();
585 s.push_str("<?xml version=\"1.0\"?>\n");
586 s.push_str(
587 "<VTKFile type=\"vtkMultiBlockDataSet\" version=\"1.0\" byte_order=\"LittleEndian\">\n",
588 );
589 s.push_str(" <vtkMultiBlockDataSet>\n");
590 for (i, block) in self.blocks.iter().enumerate() {
591 s.push_str(&format!(
592 " <DataSet index=\"{}\" name=\"{}\" file=\"{}.vtu\"/>\n",
593 i, block.name, block.name
594 ));
595 }
596 s.push_str(" </vtkMultiBlockDataSet>\n");
597 s.push_str("</VTKFile>\n");
598 s
599 }
600 pub fn write_to_dir(&self, dir: &str) -> crate::Result<Vec<String>> {
604 use std::io::Write;
605 let mut written = Vec::new();
606 for block in &self.blocks {
607 let path = format!("{}/{}.vtu", dir, block.name);
608 let xml = block.grid.to_vtu_string();
609 let mut f = std::fs::File::create(&path)?;
610 f.write_all(xml.as_bytes())?;
611 written.push(path);
612 }
613 let vtm_path = format!("{}/{}.vtm", dir, self.title);
614 let vtm = self.to_vtm_string();
615 let mut f = std::fs::File::create(&vtm_path)?;
616 f.write_all(vtm.as_bytes())?;
617 written.push(vtm_path);
618 Ok(written)
619 }
620}
621#[allow(dead_code)]
623pub struct VtkPolyDataGrid {
624 pub points: Vec<[f64; 3]>,
626 pub lines: Vec<[usize; 2]>,
628 pub triangles: Vec<[usize; 3]>,
630 pub point_data: Vec<VtkDataArray>,
632 pub cell_data: Vec<VtkDataArray>,
634}
635impl VtkPolyDataGrid {
636 pub fn new() -> Self {
638 Self {
639 points: Vec::new(),
640 lines: Vec::new(),
641 triangles: Vec::new(),
642 point_data: Vec::new(),
643 cell_data: Vec::new(),
644 }
645 }
646 pub fn add_point(&mut self, p: [f64; 3]) -> usize {
648 let idx = self.points.len();
649 self.points.push(p);
650 idx
651 }
652 pub fn add_triangle(&mut self, a: usize, b: usize, c: usize) {
654 self.triangles.push([a, b, c]);
655 }
656 pub fn add_line(&mut self, a: usize, b: usize) {
658 self.lines.push([a, b]);
659 }
660 pub fn n_points(&self) -> usize {
662 self.points.len()
663 }
664 pub fn n_triangles(&self) -> usize {
666 self.triangles.len()
667 }
668 pub fn compute_triangle_normals(&self) -> Vec<[f64; 3]> {
670 self.triangles
671 .iter()
672 .map(|&[a, b, c]| {
673 let pa = self.points[a];
674 let pb = self.points[b];
675 let pc = self.points[c];
676 let ab = [pb[0] - pa[0], pb[1] - pa[1], pb[2] - pa[2]];
677 let ac = [pc[0] - pa[0], pc[1] - pa[1], pc[2] - pa[2]];
678 let n = [
679 ab[1] * ac[2] - ab[2] * ac[1],
680 ab[2] * ac[0] - ab[0] * ac[2],
681 ab[0] * ac[1] - ab[1] * ac[0],
682 ];
683 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
684 if len < 1e-12 {
685 [0.0, 0.0, 1.0]
686 } else {
687 [n[0] / len, n[1] / len, n[2] / len]
688 }
689 })
690 .collect()
691 }
692 pub fn to_vtu_grid(&self) -> VtuGrid {
694 let mut g = VtuGrid::new();
695 for &p in &self.points {
696 g.add_point(p);
697 }
698 for &[a, b, c] in &self.triangles {
699 g.add_cell(vec![a, b, c], VtkCellType::Triangle);
700 }
701 for &[a, b] in &self.lines {
702 g.add_cell(vec![a, b], VtkCellType::Line);
703 }
704 g
705 }
706}
707#[derive(Debug, Clone)]
709pub enum VtkDataArray {
710 Scalar {
712 name: String,
714 values: Vec<f64>,
716 },
717 Vector3 {
719 name: String,
721 values: Vec<[f64; 3]>,
723 },
724 Integer {
726 name: String,
728 values: Vec<i64>,
730 },
731}
732impl VtkDataArray {
733 pub fn name(&self) -> &str {
735 match self {
736 Self::Scalar { name, .. } => name,
737 Self::Vector3 { name, .. } => name,
738 Self::Integer { name, .. } => name,
739 }
740 }
741 pub fn n_components(&self) -> usize {
743 match self {
744 Self::Scalar { .. } => 1,
745 Self::Vector3 { .. } => 3,
746 Self::Integer { .. } => 1,
747 }
748 }
749 pub fn len(&self) -> usize {
751 match self {
752 Self::Scalar { values, .. } => values.len(),
753 Self::Vector3 { values, .. } => values.len(),
754 Self::Integer { values, .. } => values.len(),
755 }
756 }
757 pub fn is_empty(&self) -> bool {
759 self.len() == 0
760 }
761}
762#[allow(dead_code)]
764pub struct VtkBlock {
765 pub name: String,
767 pub grid: VtuGrid,
769}
770impl VtkBlock {
771 pub fn new(name: impl Into<String>, grid: VtuGrid) -> Self {
773 Self {
774 name: name.into(),
775 grid,
776 }
777 }
778}
779#[allow(dead_code)]
781pub struct VtkRectilinearGrid {
782 pub x_coords: Vec<f64>,
784 pub y_coords: Vec<f64>,
786 pub z_coords: Vec<f64>,
788 pub point_data: Vec<VtkDataArray>,
790}
791impl VtkRectilinearGrid {
792 pub fn new(x_coords: Vec<f64>, y_coords: Vec<f64>, z_coords: Vec<f64>) -> Self {
794 Self {
795 x_coords,
796 y_coords,
797 z_coords,
798 point_data: Vec::new(),
799 }
800 }
801 pub fn n_points(&self) -> usize {
803 self.x_coords.len() * self.y_coords.len() * self.z_coords.len()
804 }
805 pub fn dims(&self) -> [usize; 3] {
807 [
808 self.x_coords.len(),
809 self.y_coords.len(),
810 self.z_coords.len(),
811 ]
812 }
813 pub fn add_point_scalar(&mut self, name: &str, values: Vec<f64>) {
815 self.point_data.push(VtkDataArray::Scalar {
816 name: name.to_owned(),
817 values,
818 });
819 }
820 pub fn to_vtr_string(&self) -> String {
822 let [ni, nj, nk] = self.dims();
823 let mut s = String::new();
824 s.push_str("<?xml version=\"1.0\"?>\n");
825 s.push_str(
826 "<VTKFile type=\"RectilinearGrid\" version=\"0.1\" byte_order=\"LittleEndian\">\n",
827 );
828 s.push_str(&format!(
829 " <RectilinearGrid WholeExtent=\"0 {} 0 {} 0 {}\">\n",
830 ni.saturating_sub(1),
831 nj.saturating_sub(1),
832 nk.saturating_sub(1)
833 ));
834 s.push_str(&format!(
835 " <Piece Extent=\"0 {} 0 {} 0 {}\">\n",
836 ni.saturating_sub(1),
837 nj.saturating_sub(1),
838 nk.saturating_sub(1)
839 ));
840 s.push_str(" <Coordinates>\n");
841 for (label, coords) in [
842 ("x", &self.x_coords),
843 ("y", &self.y_coords),
844 ("z", &self.z_coords),
845 ] {
846 s.push_str(&format!(
847 " <DataArray type=\"Float64\" Name=\"{}\" format=\"ascii\">\n ",
848 label
849 ));
850 for (i, v) in coords.iter().enumerate() {
851 if i > 0 {
852 s.push(' ');
853 }
854 s.push_str(&v.to_string());
855 }
856 s.push_str("\n </DataArray>\n");
857 }
858 s.push_str(" </Coordinates>\n");
859 if !self.point_data.is_empty() {
860 s.push_str(" <PointData>\n");
861 for arr in &self.point_data {
862 if let VtkDataArray::Scalar { name, values } = arr {
863 s.push_str(
864 &format!(
865 " <DataArray type=\"Float64\" Name=\"{}\" format=\"ascii\">\n ",
866 name
867 ),
868 );
869 for (i, v) in values.iter().enumerate() {
870 if i > 0 {
871 s.push(' ');
872 }
873 s.push_str(&v.to_string());
874 }
875 s.push_str("\n </DataArray>\n");
876 }
877 }
878 s.push_str(" </PointData>\n");
879 }
880 s.push_str(" </Piece>\n </RectilinearGrid>\n</VTKFile>\n");
881 s
882 }
883}
884#[allow(dead_code)]
889pub struct VtkStructuredGrid {
890 pub dims: [usize; 3],
892 pub points: Vec<[f64; 3]>,
894 pub point_data: Vec<VtkDataArray>,
896}
897impl VtkStructuredGrid {
898 pub fn new(ni: usize, nj: usize, nk: usize) -> Self {
900 Self {
901 dims: [ni, nj, nk],
902 points: Vec::with_capacity(ni * nj * nk),
903 point_data: Vec::new(),
904 }
905 }
906 pub fn n_points(&self) -> usize {
908 self.dims[0] * self.dims[1] * self.dims[2]
909 }
910 pub fn add_point_scalar(&mut self, name: &str, values: Vec<f64>) {
912 self.point_data.push(VtkDataArray::Scalar {
913 name: name.to_owned(),
914 values,
915 });
916 }
917 pub fn to_vts_string(&self) -> String {
919 let mut s = String::new();
920 s.push_str("<?xml version=\"1.0\"?>\n");
921 s.push_str(
922 "<VTKFile type=\"StructuredGrid\" version=\"0.1\" byte_order=\"LittleEndian\">\n",
923 );
924 s.push_str(&format!(
925 " <StructuredGrid WholeExtent=\"0 {} 0 {} 0 {}\">\n",
926 self.dims[0].saturating_sub(1),
927 self.dims[1].saturating_sub(1),
928 self.dims[2].saturating_sub(1)
929 ));
930 s.push_str(&format!(
931 " <Piece Extent=\"0 {} 0 {} 0 {}\">\n",
932 self.dims[0].saturating_sub(1),
933 self.dims[1].saturating_sub(1),
934 self.dims[2].saturating_sub(1)
935 ));
936 s.push_str(" <Points>\n");
937 s.push_str(
938 " <DataArray type=\"Float64\" NumberOfComponents=\"3\" format=\"ascii\">\n",
939 );
940 for p in &self.points {
941 s.push_str(&format!(" {} {} {}\n", p[0], p[1], p[2]));
942 }
943 s.push_str(" </DataArray>\n </Points>\n");
944 if !self.point_data.is_empty() {
945 s.push_str(" <PointData>\n");
946 for arr in &self.point_data {
947 if let VtkDataArray::Scalar { name, values } = arr {
948 s.push_str(
949 &format!(
950 " <DataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"1\" format=\"ascii\">\n ",
951 name
952 ),
953 );
954 for (i, v) in values.iter().enumerate() {
955 if i > 0 {
956 s.push(' ');
957 }
958 s.push_str(&v.to_string());
959 }
960 s.push_str("\n </DataArray>\n");
961 }
962 }
963 s.push_str(" </PointData>\n");
964 }
965 s.push_str(" </Piece>\n </StructuredGrid>\n</VTKFile>\n");
966 s
967 }
968 #[allow(clippy::too_many_arguments)]
970 pub fn uniform(
971 x0: f64,
972 x1: f64,
973 ni: usize,
974 y0: f64,
975 y1: f64,
976 nj: usize,
977 z0: f64,
978 z1: f64,
979 nk: usize,
980 ) -> Self {
981 let mut g = Self::new(ni, nj, nk);
982 let dx = if ni > 1 {
983 (x1 - x0) / (ni - 1) as f64
984 } else {
985 0.0
986 };
987 let dy = if nj > 1 {
988 (y1 - y0) / (nj - 1) as f64
989 } else {
990 0.0
991 };
992 let dz = if nk > 1 {
993 (z1 - z0) / (nk - 1) as f64
994 } else {
995 0.0
996 };
997 for k in 0..nk {
998 for j in 0..nj {
999 for i in 0..ni {
1000 g.points
1001 .push([x0 + i as f64 * dx, y0 + j as f64 * dy, z0 + k as f64 * dz]);
1002 }
1003 }
1004 }
1005 g
1006 }
1007}