Skip to content

Commit 44e7063

Browse files
committed
Implement loading of meshes from MSH files
1 parent 5ebfb82 commit 44e7063

File tree

4 files changed

+245
-0
lines changed

4 files changed

+245
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ fenris-sparse = { version="0.0.1", path = "fenris-sparse" }
4040
fenris-geometry = { version= "^0.0.3", path = "fenris-geometry", features = [ "proptest" ] }
4141
fenris-optimize = { version="0.0.1", path = "fenris-optimize" }
4242
fenris-quadrature = { version="0.0.2", path = "fenris-quadrature" }
43+
mshio = "0.4.2"
4344

4445
[dev-dependencies]
4546
nalgebra = { version = "0.28", features = [ "serde-serialize", "compare" ] }

assets/meshes/sphere_593.msh

39 KB
Binary file not shown.

src/io/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod msh;
12
pub mod vtk;

src/io/msh.rs

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
use crate::connectivity::{Tet4Connectivity, Tri3d2Connectivity, Tri3d3Connectivity};
2+
use crate::mesh::Mesh;
3+
use eyre::{eyre, Context};
4+
use log::warn;
5+
use nalgebra::allocator::Allocator;
6+
use nalgebra::{DefaultAllocator, DimName, OPoint, Point2, Point3, RealField, U2, U3};
7+
use std::path::Path;
8+
9+
/// Loads a [`Mesh`] from a Gmsh MSH file at the given path.
10+
pub fn load_msh_from_file<T, D, C, P: AsRef<Path>>(file_path: P) -> eyre::Result<Mesh<T, D, C>>
11+
where
12+
T: RealField,
13+
D: DimName,
14+
C: TryConnectivityFromMshElement,
15+
OPoint<T, D>: TryVertexFromMshNode<T, D, f64>,
16+
DefaultAllocator: Allocator<T, D>,
17+
{
18+
let msh_bytes = std::fs::read(file_path).wrap_err("failed to read file")?;
19+
load_msh_from_bytes(&msh_bytes).wrap_err("failed to load mesh from msh file")
20+
}
21+
22+
/// Loads a [`Mesh`] by parsing the given bytes as a Gmsh MSH file.
23+
pub fn load_msh_from_bytes<T, D, C>(bytes: &[u8]) -> eyre::Result<Mesh<T, D, C>>
24+
where
25+
T: RealField,
26+
D: DimName,
27+
C: TryConnectivityFromMshElement,
28+
OPoint<T, D>: TryVertexFromMshNode<T, D, f64>,
29+
DefaultAllocator: Allocator<T, D>,
30+
{
31+
let mut msh_file = mshio::parse_msh_bytes(bytes).map_err(|e| eyre!("failed to parse msh file: {}", e))?;
32+
33+
let msh_nodes = msh_file
34+
.data
35+
.nodes
36+
.take()
37+
.ok_or(eyre!("MSH file does not contain nodes"))?;
38+
let msh_elements = msh_file
39+
.data
40+
.elements
41+
.take()
42+
.ok_or(eyre!("MSH file does not contain elements"))?;
43+
44+
let mut vertices = Vec::new();
45+
let mut connectivity = Vec::new();
46+
47+
for node_block in &msh_nodes.node_blocks {
48+
let block_vertices = vertices_from_node_block(node_block)?;
49+
vertices.extend(block_vertices);
50+
}
51+
52+
for element_block in &msh_elements.element_blocks {
53+
let block_connectivity = connectivity_from_element_block(element_block)?;
54+
connectivity.extend(block_connectivity);
55+
}
56+
57+
Ok(Mesh::from_vertices_and_connectivity(vertices, connectivity))
58+
}
59+
60+
/// Tries to convert a `mshio::NodeBlock` to a `Vec<OPoint<T, D>>`.
61+
fn vertices_from_node_block<T, D, F, I>(node_block: &mshio::NodeBlock<u64, I, F>) -> eyre::Result<Vec<OPoint<T, D>>>
62+
where
63+
T: RealField,
64+
D: DimName,
65+
F: mshio::MshFloatT,
66+
I: mshio::MshIntT,
67+
OPoint<T, D>: TryVertexFromMshNode<T, D, F>,
68+
DefaultAllocator: Allocator<T, D>,
69+
{
70+
// Ensure that node tags are consecutive
71+
if node_block.node_tags.is_some() {
72+
return Err(eyre!("node block tags are not consecutive in msh file"));
73+
}
74+
75+
// Check dimension of node block vertices
76+
if node_block
77+
.entity_dim
78+
.to_usize()
79+
.ok_or_else(|| eyre!("error converting node block entity dimension to usize"))?
80+
!= D::dim()
81+
{
82+
// TODO: When can this happen?
83+
warn!("Node block entity does not have the right dimension for this mesh. Will be read as if they were of the same dimension.");
84+
//return Err(eyre!("node block entity does not have the right dimension for this mesh"));
85+
}
86+
87+
let mut vertices = Vec::with_capacity(node_block.nodes.len());
88+
89+
// Convert MSH vertices to points
90+
for node in &node_block.nodes {
91+
vertices.push(OPoint::try_vertex_from_msh_node(node)?);
92+
}
93+
94+
Ok(vertices)
95+
}
96+
97+
/// Tries to convert a `mshio::ElementBlock` to a `Vec<Connectivity>`.
98+
fn connectivity_from_element_block<C, I>(element_block: &mshio::ElementBlock<u64, I>) -> eyre::Result<Vec<C>>
99+
where
100+
C: TryConnectivityFromMshElement,
101+
I: mshio::MshIntT,
102+
{
103+
// Ensure that element tags are consecutive
104+
if element_block.element_tags.is_some() {
105+
return Err(eyre!("element block tags are not consecutive in msh file"));
106+
}
107+
108+
let requested_msh_element_type = C::msh_element_type();
109+
if element_block.element_type != requested_msh_element_type {
110+
warn!(
111+
"Detected connectivity in the MSH file that does not match the requested connectivity. It will be ignored."
112+
);
113+
return Ok(Vec::new());
114+
//return Err(eyre!("connectivity in the MSH file does not match the requested connectivity."));
115+
} else {
116+
let mut connectivity = Vec::with_capacity(element_block.elements.len());
117+
let requested_nodes = requested_msh_element_type
118+
.nodes()
119+
.map_err(|_| eyre!("unimplemented element type requested"))?;
120+
121+
for element in &element_block.elements {
122+
if element.nodes.len() < requested_nodes {
123+
return Err(eyre!("Not enough nodes to initialize connectivity."));
124+
}
125+
connectivity.push(C::try_connectivity_from_msh_element(element)?);
126+
}
127+
128+
return Ok(connectivity);
129+
}
130+
}
131+
132+
/// Allows conversion from `mshio::Node`s to `OPoint`s which are used as vertices in `fenris`.
133+
pub trait TryVertexFromMshNode<T, D, F>
134+
where
135+
Self: Sized,
136+
T: RealField,
137+
D: DimName,
138+
F: mshio::MshFloatT,
139+
DefaultAllocator: Allocator<T, D>,
140+
{
141+
fn try_vertex_from_msh_node(node: &mshio::Node<F>) -> eyre::Result<OPoint<T, D>>;
142+
}
143+
144+
macro_rules! f_to_t {
145+
($component:expr) => {
146+
T::from_f64(
147+
$component
148+
.to_f64()
149+
.ok_or_else(|| eyre!("failed to convert coordinate to f64"))?,
150+
)
151+
.ok_or_else(|| eyre!("failed to convert node coordinate from f64 to target mesh real type"))?
152+
};
153+
}
154+
155+
impl<T, F> TryVertexFromMshNode<T, U2, F> for Point2<T>
156+
where
157+
T: RealField,
158+
F: mshio::MshFloatT,
159+
{
160+
fn try_vertex_from_msh_node(node: &mshio::Node<F>) -> eyre::Result<Self> {
161+
// TODO: Ensure that node.z is zero?
162+
Ok(Self::new(f_to_t!(node.x), f_to_t!(node.y)))
163+
}
164+
}
165+
166+
impl<T, F> TryVertexFromMshNode<T, U3, F> for Point3<T>
167+
where
168+
T: RealField,
169+
F: mshio::MshFloatT,
170+
{
171+
fn try_vertex_from_msh_node(node: &mshio::Node<F>) -> eyre::Result<Self> {
172+
Ok(Self::new(f_to_t!(node.x), f_to_t!(node.y), f_to_t!(node.z)))
173+
}
174+
}
175+
176+
/// Allows conversion from `mshio::Element`s to connectivity types used in `fenris`.
177+
pub trait TryConnectivityFromMshElement
178+
where
179+
Self: Sized,
180+
{
181+
fn msh_element_type() -> mshio::ElementType;
182+
183+
fn try_connectivity_from_msh_element(element: &mshio::Element<u64>) -> eyre::Result<Self>;
184+
}
185+
186+
impl TryConnectivityFromMshElement for Tri3d2Connectivity {
187+
fn msh_element_type() -> mshio::ElementType {
188+
mshio::ElementType::Tri3
189+
}
190+
191+
fn try_connectivity_from_msh_element(element: &mshio::Element<u64>) -> eyre::Result<Self> {
192+
Ok(Self([
193+
element.nodes[0] as usize - 1,
194+
element.nodes[1] as usize - 1,
195+
element.nodes[2] as usize - 1,
196+
]))
197+
}
198+
}
199+
200+
impl TryConnectivityFromMshElement for Tri3d3Connectivity {
201+
fn msh_element_type() -> mshio::ElementType {
202+
mshio::ElementType::Tri3
203+
}
204+
205+
fn try_connectivity_from_msh_element(element: &mshio::Element<u64>) -> eyre::Result<Self> {
206+
Ok(Self([
207+
element.nodes[0] as usize - 1,
208+
element.nodes[1] as usize - 1,
209+
element.nodes[2] as usize - 1,
210+
]))
211+
}
212+
}
213+
214+
impl TryConnectivityFromMshElement for Tet4Connectivity {
215+
fn msh_element_type() -> mshio::ElementType {
216+
mshio::ElementType::Tet4
217+
}
218+
219+
fn try_connectivity_from_msh_element(element: &mshio::Element<u64>) -> eyre::Result<Self> {
220+
Ok(Self([
221+
element.nodes[0] as usize - 1,
222+
element.nodes[1] as usize - 1,
223+
element.nodes[2] as usize - 1,
224+
element.nodes[3] as usize - 1,
225+
]))
226+
}
227+
}
228+
229+
#[cfg(test)]
230+
mod msh_tests {
231+
use crate::connectivity::Tet4Connectivity;
232+
use crate::io::msh::load_msh_from_file;
233+
use nalgebra::U3;
234+
235+
#[test]
236+
fn load_msh_sphere() -> eyre::Result<()> {
237+
let mesh = load_msh_from_file::<f64, U3, Tet4Connectivity, _>("assets/meshes/sphere_593.msh")?;
238+
239+
assert_eq!(mesh.vertices().len(), 183);
240+
assert_eq!(mesh.connectivity().len(), 593);
241+
Ok(())
242+
}
243+
}

0 commit comments

Comments
 (0)