1use crate::data::storage::VecStorage;
9use crate::io::{MeshData, SieveSectionReader};
10use crate::mesh_error::MeshSieveError;
11use crate::topology::cell_type::CellType;
12use crate::topology::labels::LabelSet;
13use crate::topology::point::PointId;
14use crate::topology::sieve::MeshSieve;
15use std::io::Read;
16
17#[derive(Debug, Default, Clone)]
19pub struct FluentReader;
20
21impl SieveSectionReader for FluentReader {
22 type Sieve = MeshSieve;
23 type Value = f64;
24 type Storage = VecStorage<f64>;
25 type CellStorage = VecStorage<CellType>;
26
27 fn read<R: Read>(
28 &self,
29 mut reader: R,
30 ) -> Result<MeshData<Self::Sieve, Self::Value, Self::Storage, Self::CellStorage>, MeshSieveError>
31 {
32 let mut text = String::new();
33 reader.read_to_string(&mut text)?;
34 parse_fluent_ascii(&text)
35 }
36}
37
38fn parse_fluent_ascii(
39 text: &str,
40) -> Result<MeshData<MeshSieve, f64, VecStorage<f64>, VecStorage<CellType>>, MeshSieveError> {
41 if text.lines().any(|l| l.trim_start().starts_with("vertices")) {
42 return parse_compact(text);
43 }
44 parse_sexpr_subset(text)
45}
46
47fn parse_compact(
48 text: &str,
49) -> Result<MeshData<MeshSieve, f64, VecStorage<f64>, VecStorage<CellType>>, MeshSieveError> {
50 let mut vertices = Vec::new();
51 let mut cells = Vec::new();
52 let mut labels = LabelSet::new();
53 for line in text.lines() {
54 let line = line.trim();
55 if line.is_empty() || line.starts_with('#') {
56 continue;
57 }
58 let parts: Vec<_> = line.split_whitespace().collect();
59 match parts.as_slice() {
60 ["v", x, y, z] => vertices.push([parse_f64(x)?, parse_f64(y)?, parse_f64(z)?]),
61 ["label", name, value, ids @ ..] if !ids.is_empty() => {
62 let value = value.parse::<i32>().map_err(|_| {
63 MeshSieveError::MeshIoParse(format!("invalid Fluent label value: {value}"))
64 })?;
65 for id in ids {
66 labels.set_label(PointId::new(parse_u64(id)?)?, name, value);
67 }
68 }
69 ["cell", rest @ ..] if !rest.is_empty() => {
70 let conn = rest
71 .iter()
72 .map(|v| PointId::new(parse_u64(v)?))
73 .collect::<Result<Vec<_>, _>>()?;
74 cells.push(conn);
75 }
76 ["vertices", _] | ["cells", _] => {}
77 _ => {
78 return Err(MeshSieveError::MeshIoParse(format!(
79 "unsupported Fluent compact line: {line}"
80 )));
81 }
82 }
83 }
84 let mut mesh = crate::io::ply::build_mesh(vertices, cells)?;
85 mesh.labels = (!labels.is_empty()).then_some(labels);
86 Ok(mesh)
87}
88
89fn parse_sexpr_subset(
90 text: &str,
91) -> Result<MeshData<MeshSieve, f64, VecStorage<f64>, VecStorage<CellType>>, MeshSieveError> {
92 let mut vertices: Vec<[f64; 3]> = Vec::new();
93 let mut cells: Vec<Vec<PointId>> = Vec::new();
94 let mut labels = LabelSet::new();
95 let lines: Vec<_> = text.lines().collect();
96 let mut i = 0;
97 while i < lines.len() {
98 let line = lines[i].trim();
99 if line.starts_with("(10 (") && !line.contains(" 0 ") {
100 i += 1;
101 while i < lines.len() {
102 let l = lines[i].trim().trim_matches(|c| c == '(' || c == ')');
103 if l.is_empty() {
104 i += 1;
105 break;
106 }
107 let vals: Vec<_> = l.split_whitespace().collect();
108 if vals.len() < 2 {
109 break;
110 }
111 let x = parse_hex_or_float(vals[0])?;
112 let y = parse_hex_or_float(vals[1])?;
113 let z = vals.get(2).map_or(Ok(0.0), |v| parse_hex_or_float(v))?;
114 vertices.push([x, y, z]);
115 if lines[i].contains("))") {
116 i += 1;
117 break;
118 }
119 i += 1;
120 }
121 continue;
122 }
123 if line.starts_with("(12 (") && !line.contains(" 0 ") {
124 i += 1;
125 while i < lines.len() {
126 let l = lines[i].trim().trim_matches(|c| c == '(' || c == ')');
127 if l.is_empty() {
128 i += 1;
129 break;
130 }
131 let vals: Vec<_> = l.split_whitespace().collect();
132 if vals.len() < 2 {
133 break;
134 }
135 let conn = vals
136 .iter()
137 .map(|v| PointId::new(parse_hex_u64(v)?))
138 .collect::<Result<Vec<_>, _>>()?;
139 cells.push(conn);
140 if lines[i].contains("))") {
141 i += 1;
142 break;
143 }
144 i += 1;
145 }
146 continue;
147 }
148 if line.starts_with("(13 (") && !line.contains(" 0 ") {
149 let zone_id = line
150 .split_whitespace()
151 .nth(1)
152 .and_then(|raw| raw.trim_matches('(').parse::<i32>().ok())
153 .unwrap_or(1);
154 i += 1;
155 while i < lines.len() {
156 let l = lines[i].trim().trim_matches(|c| c == '(' || c == ')');
157 if l.is_empty() {
158 i += 1;
159 break;
160 }
161 let vals: Vec<_> = l.split_whitespace().collect();
162 if vals.len() < 4 {
163 break;
164 }
165 let vertex_tokens = &vals[..vals.len().saturating_sub(2)];
166 for token in vertex_tokens {
167 let point = PointId::new(parse_hex_u64(token)?)?;
168 labels.set_label(point, "fluent:bc", zone_id);
169 labels.set_label(point, &format!("fluent:zone:{zone_id}"), 1);
170 }
171 if lines[i].contains("))") {
172 i += 1;
173 break;
174 }
175 i += 1;
176 }
177 continue;
178 }
179 i += 1;
180 }
181 if vertices.is_empty() {
182 return Err(MeshSieveError::MeshIoParse(
183 "no Fluent vertex coordinates found".into(),
184 ));
185 }
186 let mut mesh = crate::io::ply::build_mesh(vertices, cells)?;
187 mesh.labels = (!labels.is_empty()).then_some(labels);
188 Ok(mesh)
189}
190
191fn parse_f64(token: &str) -> Result<f64, MeshSieveError> {
192 token
193 .parse::<f64>()
194 .map_err(|_| MeshSieveError::MeshIoParse(format!("invalid float: {token}")))
195}
196fn parse_u64(token: &str) -> Result<u64, MeshSieveError> {
197 token
198 .parse::<u64>()
199 .map_err(|_| MeshSieveError::MeshIoParse(format!("invalid integer: {token}")))
200}
201fn parse_hex_u64(token: &str) -> Result<u64, MeshSieveError> {
202 u64::from_str_radix(token, 16)
203 .or_else(|_| token.parse::<u64>())
204 .map_err(|_| MeshSieveError::MeshIoParse(format!("invalid Fluent integer: {token}")))
205}
206fn parse_hex_or_float(token: &str) -> Result<f64, MeshSieveError> {
207 token
208 .parse::<f64>()
209 .or_else(|_| u64::from_str_radix(token, 16).map(|v| v as f64))
210 .map_err(|_| MeshSieveError::MeshIoParse(format!("invalid Fluent number: {token}")))
211}