Skip to content

Commit ad0688d

Browse files
committed
Implement loading of meshes from MSH files
1 parent e3ee365 commit ad0688d

File tree

4 files changed

+243
-0
lines changed

4 files changed

+243
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ fenris-sparse = { version= "0.0.2", path = "fenris-sparse" }
4444
fenris-geometry = { version= "^0.0.5", path = "fenris-geometry", features = [ "proptest" ] }
4545
fenris-optimize = { version= "0.0.2", path = "fenris-optimize" }
4646
fenris-quadrature = { version= "0.0.4", path = "fenris-quadrature" }
47+
mshio = "0.4.2"
4748

4849
[dev-dependencies]
4950
fenris = { path = ".", features = [ "proptest-support" ]}

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: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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+
T: RealField,
136+
D: DimName,
137+
F: mshio::MshFloatT,
138+
DefaultAllocator: Allocator<T, D>,
139+
{
140+
fn try_vertex_from_msh_node(node: &mshio::Node<F>) -> eyre::Result<OPoint<T, D>>;
141+
}
142+
143+
macro_rules! f_to_t {
144+
($component:expr) => {
145+
T::from_f64(
146+
$component
147+
.to_f64()
148+
.ok_or_else(|| eyre!("failed to convert coordinate to f64"))?,
149+
)
150+
.ok_or_else(|| eyre!("failed to convert node coordinate from f64 to target mesh real type"))?
151+
};
152+
}
153+
154+
impl<T, F> TryVertexFromMshNode<T, U2, F> for Point2<T>
155+
where
156+
T: RealField,
157+
F: mshio::MshFloatT,
158+
{
159+
fn try_vertex_from_msh_node(node: &mshio::Node<F>) -> eyre::Result<Self> {
160+
// TODO: Ensure that node.z is zero?
161+
Ok(Self::new(f_to_t!(node.x), f_to_t!(node.y)))
162+
}
163+
}
164+
165+
impl<T, F> TryVertexFromMshNode<T, U3, F> for Point3<T>
166+
where
167+
T: RealField,
168+
F: mshio::MshFloatT,
169+
{
170+
fn try_vertex_from_msh_node(node: &mshio::Node<F>) -> eyre::Result<Self> {
171+
Ok(Self::new(f_to_t!(node.x), f_to_t!(node.y), f_to_t!(node.z)))
172+
}
173+
}
174+
175+
/// Allows conversion from `mshio::Element`s to connectivity types used in `fenris`.
176+
pub trait TryConnectivityFromMshElement
177+
where
178+
Self: Sized,
179+
{
180+
fn msh_element_type() -> mshio::ElementType;
181+
fn try_connectivity_from_msh_element(element: &mshio::Element<u64>) -> eyre::Result<Self>;
182+
}
183+
184+
impl TryConnectivityFromMshElement for Tri3d2Connectivity {
185+
fn msh_element_type() -> mshio::ElementType {
186+
mshio::ElementType::Tri3
187+
}
188+
189+
fn try_connectivity_from_msh_element(element: &mshio::Element<u64>) -> eyre::Result<Self> {
190+
Ok(Self([
191+
element.nodes[0] as usize - 1,
192+
element.nodes[1] as usize - 1,
193+
element.nodes[2] as usize - 1,
194+
]))
195+
}
196+
}
197+
198+
impl TryConnectivityFromMshElement for Tri3d3Connectivity {
199+
fn msh_element_type() -> mshio::ElementType {
200+
mshio::ElementType::Tri3
201+
}
202+
203+
fn try_connectivity_from_msh_element(element: &mshio::Element<u64>) -> eyre::Result<Self> {
204+
Ok(Self([
205+
element.nodes[0] as usize - 1,
206+
element.nodes[1] as usize - 1,
207+
element.nodes[2] as usize - 1,
208+
]))
209+
}
210+
}
211+
212+
impl TryConnectivityFromMshElement for Tet4Connectivity {
213+
fn msh_element_type() -> mshio::ElementType {
214+
mshio::ElementType::Tet4
215+
}
216+
217+
fn try_connectivity_from_msh_element(element: &mshio::Element<u64>) -> eyre::Result<Self> {
218+
Ok(Self([
219+
element.nodes[0] as usize - 1,
220+
element.nodes[1] as usize - 1,
221+
element.nodes[2] as usize - 1,
222+
element.nodes[3] as usize - 1,
223+
]))
224+
}
225+
}
226+
227+
#[cfg(test)]
228+
mod msh_tests {
229+
use crate::connectivity::Tet4Connectivity;
230+
use crate::io::msh::load_msh_from_file;
231+
use nalgebra::U3;
232+
233+
#[test]
234+
fn load_msh_sphere() -> eyre::Result<()> {
235+
let mesh = load_msh_from_file::<f64, U3, Tet4Connectivity, _>("assets/meshes/sphere_593.msh")?;
236+
237+
assert_eq!(mesh.vertices().len(), 183);
238+
assert_eq!(mesh.connectivity().len(), 593);
239+
Ok(())
240+
}
241+
}

0 commit comments

Comments
 (0)