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#[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 pub material_count_per_element: Option<MaterialCountPerElement<U>>,
38
39 pub nodes: Vec<Node<U, F>>,
41
42 pub e2ls: Vec<E2L<U>>,
44 pub e3ls: Vec<E3L<U>>,
46 pub e3ts: Vec<E3T<U>>,
48 pub e6ts: Vec<E6T<U>>,
50 pub e4qs: Vec<E4Q<U>>,
52 pub e8qs: Vec<E8Q<U>>,
54 pub e9qs: Vec<E9Q<U>>,
56
57 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 match line_it.next() {
75 Some(MESH_2D_TAG) => {} 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 weak_error(Error::EmptyLine)?;
83 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 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 Err(Error::MissingCard(NODESTRING_TAG.into()))?;
121 }
122 _ => {} }
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#[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) => {} 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}