1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use crate::mesh::Constraint3d;
use crate::StrError;
use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::BufReader;
use std::path::Path;
/// Holds a collection of 3d blocks for structured mesh generation.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Blocks3d {
/// List of points
///
/// Each point is defined by x, y, and z coordinates.
///
/// (length = number of points)
pub points: Vec<(f64, f64, f64)>,
/// List of regions defining each block
///
/// Each block is defined by `(marker, p1, p2, p3, p4, p5, p6, p7, p8)` where
/// `marker` is an integer to identify a group of blocks and
/// `p1`, `p2`, `p3`, `p4`, `p5`, `p6`, `p7`, `p8` are the indices of the corner points of the block.
///
/// (length = number of blocks)
pub regions: Vec<(i32, usize, usize, usize, usize, usize, usize, usize, usize)>,
/// List of division weights for each block
///
/// Each entry is defined as `(weights_x, weights_y, weights_z)` where
/// `weights_x` is a vector of weights for divisions in the x direction,
/// `weights_y` is a vector of weights for divisions in the y direction, and
/// `weights_z` is a vector of weights for divisions in the z direction.
/// The number of divisions along each direction is determined by the length of the respective weight vector.
/// Only vectors with length > 0 are considered, otherwise default uniform divisions are used.
///
/// Note: All `weights_x`, `weights_y`, and `weights_z` are required to be > 0 when setting the number of divisions/weights.
///
/// (length = number of blocks)
pub div_weights: Vec<(Vec<f64>, Vec<f64>, Vec<f64>)>,
/// List of face constraints
///
/// Defined as `(local_face_index, constraint)` where
/// `local_face_index` is the index of the face in the block (0 to 5),
/// and `constraint` is the `Constraint3D` applied to that face
///
/// (length = number of blocks)
pub face_constraints: Vec<Option<(usize, Constraint3d)>>,
/// Holds all marked edges
///
/// Each entry contains `(marker, p1, p2)`, where `marker` is the edge marker,
/// and `p1` and `p2` are the point ids. The point ids may be unsorted.
pub marked_edges: Vec<(i32, usize, usize)>,
/// Holds all marked faces
///
/// Each entry contains `(marker, p1, p2, p3, p4)`, where `marker` is the face marker,
/// and `p1`, `p2`, `p3`, and `p4` are the point ids. The point ids may be unsorted.
/// For triangular faces, the fourth point (p4) must be set to [usize::MAX].
pub marked_faces: Vec<(i32, usize, usize, usize, usize)>,
}
impl Blocks3d {
/// Reads a JSON file containing the data
///
/// # Input
///
/// * `full_path` -- may be a String, &str, or Path
pub fn read_json<P>(full_path: &P) -> Result<Self, StrError>
where
P: AsRef<OsStr> + ?Sized,
{
let path = Path::new(full_path).to_path_buf();
let input = File::open(path).map_err(|_| "cannot open file")?;
let buffered = BufReader::new(input);
let mesh = serde_json::from_reader(buffered).map_err(|_| "cannot parse JSON file")?;
Ok(mesh)
}
/// Writes a JSON file with the data
///
/// # Input
///
/// * `full_path` -- may be a String, &str, or Path
pub fn write_json<P>(&self, full_path: &P) -> Result<(), StrError>
where
P: AsRef<OsStr> + ?Sized,
{
let path = Path::new(full_path).to_path_buf();
if let Some(p) = path.parent() {
fs::create_dir_all(p).map_err(|_| "cannot create directory")?;
}
let mut file = File::create(&path).map_err(|_| "cannot create file")?;
serde_json::to_writer(&mut file, &self).map_err(|_| "cannot write file")?;
Ok(())
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use super::Blocks3d;
use crate::mesh::Constraint3d;
#[test]
fn test_blocks3d_write_works() {
let r = 0.866;
let w = 2.0;
let h = 1.0;
let l = h / 2.0;
let m = f64::sqrt(r * r - l * l);
let blocks = Blocks3d {
points: vec![
(0.0, 0.0, 0.0), // 0
(1.0, 0.0, 0.0), // 1
(1.0, 1.0, 0.0), // 2
(0.0, 1.0, 0.0), // 3
(2.0, 0.0, 0.0), // 4
(2.0, 1.0, 0.0), // 5
(0.0, 0.0, 1.0), // 6
(1.0, 0.0, 1.0), // 7
(1.0, 1.0, 1.0), // 8
(0.0, 1.0, 1.0), // 9
(2.0, 0.0, 1.0), // 10
(2.0, 1.0, 1.0), // 11
],
regions: vec![
(1, 0, 1, 2, 3, 6, 7, 8, 9), // marker, p1, p2, p3, p4, p5, p6, p7, p8
(2, 1, 4, 5, 2, 7, 10, 11, 8),
],
div_weights: vec![
(Vec::new(), Vec::new(), Vec::new()), // default number of divisions and weighting
(vec![1.0], vec![1.0], vec![1.0]), // one division with weight 1.0 along each direction
],
face_constraints: vec![
None, // block 0
Some((0, Constraint3d::CylinderZ(w + m, l, r))), // block 1
],
marked_edges: Vec::new(),
marked_faces: Vec::new(),
};
assert_eq!(blocks.points.len(), 12);
assert_eq!(blocks.regions.len(), 2);
assert_eq!(blocks.div_weights.len(), 2);
assert_eq!(blocks.face_constraints.len(), 2);
blocks.write_json("/tmp/gemlab/test_blocks3d_write_works.json").unwrap();
let loaded_blocks = Blocks3d::read_json("/tmp/gemlab/test_blocks3d_write_works.json").unwrap();
assert_eq!(&loaded_blocks.points, &blocks.points);
assert_eq!(&loaded_blocks.regions, &blocks.regions);
assert_eq!(&loaded_blocks.div_weights, &blocks.div_weights);
assert_eq!(&loaded_blocks.face_constraints.len(), &blocks.face_constraints.len());
for i in 0..blocks.face_constraints.len() {
if let Some(c) = &blocks.face_constraints[i] {
assert_eq!(c.0, loaded_blocks.face_constraints[i].as_ref().unwrap().0);
}
}
}
}