sms_2dm/
lib.rs

1mod element;
2mod error;
3mod node;
4mod nodestring;
5
6use std::{fmt, iter, str::FromStr};
7
8use num_traits::{Float, Unsigned};
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12pub use crate::{element::*, error::Error, node::Node, nodestring::Nodestring};
13use crate::{error::weak_error, node::NODE_TAG, nodestring::NODESTRING_TAG};
14
15type DefaultUnsigned = u32;
16type DefaultFloat = f64;
17
18pub(crate) const MESH_2D_TAG: &str = "MESH2D";
19
20/// Representation of a `.2dm` file, as defined in https://www.xmswiki.com/wiki/SMS:2D_Mesh_Files_*.2dm.
21///
22/// You can optionally specify the types of unsigned integers and floats used in this struct using
23/// generic types `U` and `F` respectively. The defaults are `U = u32` and `F = f64`.
24#[derive(Clone, Debug, PartialEq)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26pub struct Mesh2D<U = DefaultUnsigned, F = DefaultFloat>
27where
28    U: Unsigned + Copy + fmt::Debug,
29    F: Float + fmt::Debug,
30    Error: From<U::FromStrRadixErr> + From<F::FromStrRadixErr>,
31{
32    /// Defines number of materials per element.
33    ///
34    /// Corresponds to the card `NUM_MATERIALS_PER_ELEM`.
35    ///
36    /// This card is mandatory, but did not exist prior to v11.0.
37    pub material_count_per_element: Option<MaterialCountPerElement<U>>,
38
39    /// All nodes defined by the mesh.
40    pub nodes: Vec<Node<U, F>>,
41
42    /// All 2-noded linear elements defined by the mesh.
43    pub e2ls: Vec<E2L<U>>,
44    /// All 3-noded linear elements defined by the mesh.
45    pub e3ls: Vec<E3L<U>>,
46    /// All 3-noded triangular elements defined by the mesh.
47    pub e3ts: Vec<E3T<U>>,
48    /// All 6-noded triangular elements defined by the mesh.
49    pub e6ts: Vec<E6T<U>>,
50    /// All 4-noded quadrilateral elements defined by the mesh.
51    pub e4qs: Vec<E4Q<U>>,
52    /// All 8-noded quadrilateral elements defined by the mesh.
53    pub e8qs: Vec<E8Q<U>>,
54    /// All 9-noded quadrilateral elements defined by the mesh.
55    pub e9qs: Vec<E9Q<U>>,
56
57    /// All nodestrings defined by the mesh.
58    pub nodestring: Vec<Nodestring<U>>,
59}
60impl<U, F> FromStr for Mesh2D<U, F>
61where
62    U: Unsigned + Copy + fmt::Debug,
63    F: Float + fmt::Debug,
64    Error: From<U::FromStrRadixErr> + From<F::FromStrRadixErr>,
65{
66    type Err = Error;
67
68    fn from_str(s: &str) -> Result<Self, Self::Err> {
69        let mut mesh = Mesh2D::new();
70
71        let mut line_it = s.lines();
72
73        // first line must be `MESH2D`
74        match line_it.next() {
75            Some(MESH_2D_TAG) => {} // continue
76            Some(_) | None => weak_error(Error::MissingCard(MESH_2D_TAG.into()))?,
77        }
78
79        while let Some(line) = line_it.next() {
80            let Some(card_type) = line.split_whitespace().next() else {
81                // empty line
82                weak_error(Error::EmptyLine)?;
83                // skip this line if we don't hard error
84                continue;
85            };
86
87            macro_rules! parse_push {
88                ($field: ident) => {{
89                    let val = line.parse()?;
90                    mesh.$field.push(val);
91                }};
92            }
93            match card_type {
94                MATERIAL_COUNT_PER_ELEMENT_TAG => {
95                    // check duplicate
96                    if mesh.material_count_per_element.is_some() {
97                        weak_error(Error::ExtraneousCard(MATERIAL_COUNT_PER_ELEMENT_TAG.into()))?;
98                    }
99                    let val = line.parse()?;
100                    let _ = mesh.material_count_per_element.insert(val);
101                }
102                NODE_TAG => parse_push!(nodes),
103                E2L_TAG => parse_push!(e2ls),
104                E3L_TAG => parse_push!(e3ls),
105                E3T_TAG => parse_push!(e3ts),
106                E6T_TAG => parse_push!(e6ts),
107                E4Q_TAG => parse_push!(e4qs),
108                E8Q_TAG => parse_push!(e8qs),
109                E9Q_TAG => parse_push!(e9qs),
110                NODESTRING_TAG => 'ns_end: {
111                    let mut ns = Nodestring::new();
112                    for line in iter::once(line).chain(&mut line_it) {
113                        if ns.ingest(line)?.is_break() {
114                            mesh.nodestring.push(ns);
115                            break 'ns_end;
116                        }
117                    }
118                    // no more lines without seeing a tail node
119                    // always a hard error because it's too much of a mess otherwise
120                    Err(Error::MissingCard(NODESTRING_TAG.into()))?;
121                }
122                _ => {} // TODO: other cards ignored for now; PRs welcomed
123            }
124        }
125
126        Ok(mesh)
127    }
128}
129impl<U, F> Mesh2D<U, F>
130where
131    U: Unsigned + Copy + fmt::Debug,
132    F: Float + fmt::Debug,
133    Error: From<U::FromStrRadixErr> + From<F::FromStrRadixErr>,
134{
135    fn new() -> Self {
136        Self {
137            material_count_per_element: None,
138            nodes: vec![],
139            e2ls: vec![],
140            e3ls: vec![],
141            e3ts: vec![],
142            e6ts: vec![],
143            e4qs: vec![],
144            e8qs: vec![],
145            e9qs: vec![],
146            nodestring: vec![],
147        }
148    }
149}
150
151pub(crate) const MATERIAL_COUNT_PER_ELEMENT_TAG: &str = "NUM_MATERIALS_PER_ELEM";
152
153/// Defines number of materials per element.
154///
155/// Corresponds to the card ``NUM_MATERIALS_PER_ELEM`.`
156#[derive(Copy, Clone, Debug, PartialEq)]
157#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
158pub struct MaterialCountPerElement<U = DefaultUnsigned>(pub U)
159where
160    U: Unsigned + Copy + fmt::Debug,
161    Error: From<U::FromStrRadixErr>;
162impl<U> FromStr for MaterialCountPerElement<U>
163where
164    U: Unsigned + Copy + fmt::Debug,
165    Error: From<U::FromStrRadixErr>,
166{
167    type Err = Error;
168
169    fn from_str(s: &str) -> Result<Self, Self::Err> {
170        let mut field_it = s.split_whitespace();
171
172        match field_it.next() {
173            Some(MATERIAL_COUNT_PER_ELEMENT_TAG) => {} // tag matches, continue
174            Some(t) => Err(Error::WrongCardTag {
175                expect: MATERIAL_COUNT_PER_ELEMENT_TAG.into(),
176                actual: t.into(),
177            })?,
178            None => Err(Error::EmptyLine)?,
179        }
180
181        let count_raw = field_it.next().ok_or(Error::MissingValue)?;
182        let count = U::from_str_radix(count_raw, 10)?;
183
184        if let Some(v) = field_it.next() {
185            weak_error(Error::ExtraneousValue(v.into()))?;
186        }
187
188        Ok(Self(count))
189    }
190}
191
192#[cfg(test)]
193mod test {
194    use std::fs;
195
196    use super::*;
197
198    #[test]
199    fn multi_line_nodestring_parse() -> anyhow::Result<()> {
200        let input = fs::read_to_string("test-res/multi-line-nodestring.2dm")?;
201        let mesh = <Mesh2D>::from_str(&input)?;
202
203        assert_eq!(mesh.nodestring.len(), 1);
204
205        Ok(())
206    }
207
208    #[test]
209    fn sample_file_2_parse() -> anyhow::Result<()> {
210        let input = fs::read_to_string("test-res/sample-2.2dm")?;
211        let mesh = <Mesh2D>::from_str(&input)?;
212
213        assert!(mesh.material_count_per_element.is_none());
214        assert_eq!(mesh.nodes.len(), 6);
215        assert_eq!(mesh.e2ls.len(), 0);
216        assert_eq!(mesh.e3ls.len(), 0);
217        assert_eq!(mesh.e3ts.len(), 3);
218        assert_eq!(mesh.e6ts.len(), 0);
219        assert_eq!(mesh.e4qs.len(), 3);
220        assert_eq!(mesh.e8qs.len(), 0);
221        assert_eq!(mesh.e9qs.len(), 0);
222        assert_eq!(mesh.nodestring.len(), 4);
223
224        Ok(())
225    }
226}