honeycomb_core/cmap/builder/
io.rs1use std::collections::{BTreeMap, HashMap};
2
3use itertools::multizip;
4use num_traits::Zero;
5use vtkio::model::{CellType, DataSet, VertexNumbers};
6use vtkio::{IOBuffer, Vtk};
7
8use crate::attributes::AttrStorageManager;
9use crate::cmap::{BuilderError, CMap2, CMapBuilder, DartIdType, VertexIdType};
10use crate::geometry::{CoordsFloat, Vertex2};
11
12pub(crate) struct CMapFile {
15 pub meta: (String, usize, usize),
16 pub betas: String,
17 pub unused: Option<String>,
18 pub vertices: Option<String>,
19}
20
21pub(crate) fn parse_meta(meta_line: &str) -> Result<(String, usize, usize), BuilderError> {
22 let parts: Vec<&str> = meta_line.split_whitespace().collect();
23 if parts.len() != 3 {
24 return Err(BuilderError::BadMetaData("incorrect format"));
25 }
26
27 Ok((
28 parts[0].to_string(),
29 parts[1]
30 .parse()
31 .map_err(|_| BuilderError::BadMetaData("could not parse dimension"))?,
32 parts[2]
33 .parse()
34 .map_err(|_| BuilderError::BadMetaData("could not parse dart number"))?,
35 ))
36}
37
38impl TryFrom<String> for CMapFile {
39 type Error = BuilderError;
40
41 fn try_from(value: String) -> Result<Self, Self::Error> {
42 let mut sections = HashMap::new();
43 let mut current_section = String::new();
44
45 for line in value.trim().lines() {
46 let trimmed = line.trim();
47 if trimmed.is_empty() || trimmed.starts_with('#') {
48 continue;
50 }
51 if trimmed.starts_with('[') && trimmed.contains(']') {
52 let section_name = trimmed.trim_matches(['[', ']']).to_lowercase();
54
55 if section_name != "meta"
56 && section_name != "betas"
57 && section_name != "unused"
58 && section_name != "vertices"
59 {
60 return Err(BuilderError::UnknownHeader(section_name));
61 }
62
63 if sections
64 .insert(section_name.clone(), String::new())
65 .is_some()
66 {
67 return Err(BuilderError::DuplicatedSection(section_name));
68 }
69 current_section = section_name;
70
71 continue;
72 }
73 if !current_section.is_empty() {
74 let line_without_comment = trimmed.split('#').next().unwrap().trim();
76 if !line_without_comment.is_empty() {
77 let current_content = sections.get_mut(¤t_section).unwrap();
78 if !current_content.is_empty() {
79 current_content.push('\n');
80 }
81 current_content.push_str(line_without_comment);
82 }
83 }
84 }
85
86 if !sections.contains_key("meta") {
87 return Err(BuilderError::MissingSection("meta"));
89 }
90 if !sections.contains_key("betas") {
91 return Err(BuilderError::MissingSection("betas"));
93 }
94
95 Ok(Self {
96 meta: parse_meta(sections["meta"].as_str())?,
97 betas: sections["betas"].clone(),
98 unused: sections.get("unused").cloned(),
99 vertices: sections.get("vertices").cloned(),
100 })
101 }
102}
103
104pub fn build_2d_from_cmap_file<T: CoordsFloat>(
107 f: CMapFile,
108 manager: AttrStorageManager, ) -> Result<CMap2<T>, BuilderError> {
110 if f.meta.1 != 2 {
111 return Err(BuilderError::BadMetaData(
113 "mismatch between requested dimension and header",
114 ));
115 }
116 let mut map = CMap2::new_with_undefined_attributes(f.meta.2, manager);
117
118 let betas = f.betas.lines().collect::<Vec<_>>();
120 if betas.len() != 3 {
121 return Err(BuilderError::InconsistentData(
123 "wrong number of beta functions",
124 ));
125 }
126 let b0 = betas[0]
127 .split_whitespace()
128 .map(str::parse)
129 .collect::<Vec<_>>();
130 let b1 = betas[1]
131 .split_whitespace()
132 .map(str::parse)
133 .collect::<Vec<_>>();
134 let b2 = betas[2]
135 .split_whitespace()
136 .map(str::parse)
137 .collect::<Vec<_>>();
138
139 if b0.len() != f.meta.2 + 1 {
141 return Err(BuilderError::InconsistentData(
142 "wrong number of values for the beta 0 function",
143 ));
144 }
145 if b1.len() != f.meta.2 + 1 {
146 return Err(BuilderError::InconsistentData(
147 "wrong number of values for the beta 1 function",
148 ));
149 }
150 if b2.len() != f.meta.2 + 1 {
151 return Err(BuilderError::InconsistentData(
152 "wrong number of values for the beta 2 function",
153 ));
154 }
155
156 for (d, b0d, b1d, b2d) in multizip((
157 (1..=f.meta.2),
158 b0.into_iter().skip(1),
159 b1.into_iter().skip(1),
160 b2.into_iter().skip(1),
161 )) {
162 let b0d = b0d.map_err(|_| BuilderError::BadValue("could not parse a b0 value"))?;
163 let b1d = b1d.map_err(|_| BuilderError::BadValue("could not parse a b1 value"))?;
164 let b2d = b2d.map_err(|_| BuilderError::BadValue("could not parse a b2 value"))?;
165 map.set_betas(d as DartIdType, [b0d, b1d, b2d]);
166 }
167
168 if let Some(unused) = f.unused {
169 for u in unused.split_whitespace() {
170 let d = u
171 .parse()
172 .map_err(|_| BuilderError::BadValue("could not parse an unused ID"))?;
173 map.remove_free_dart(d);
174 }
175 }
176
177 if let Some(vertices) = f.vertices {
178 for l in vertices.trim().lines() {
179 let mut it = l.split_whitespace();
180 let id: VertexIdType = it
181 .next()
182 .ok_or(BuilderError::BadValue("incorrect vertex line format"))?
183 .parse()
184 .map_err(|_| BuilderError::BadValue("could not parse vertex ID"))?;
185 let x: f64 = it
186 .next()
187 .ok_or(BuilderError::BadValue("incorrect vertex line format"))?
188 .parse()
189 .map_err(|_| BuilderError::BadValue("could not parse vertex x coordinate"))?;
190 let y: f64 = it
191 .next()
192 .ok_or(BuilderError::BadValue("incorrect vertex line format"))?
193 .parse()
194 .map_err(|_| BuilderError::BadValue("could not parse vertex y coordinate"))?;
195 if it.next().is_some() {
196 return Err(BuilderError::BadValue("incorrect vertex line format"));
197 }
198 map.force_write_vertex(id, (T::from(x).unwrap(), T::from(y).unwrap()));
199 }
200 }
201
202 Ok(map)
203}
204
205impl<T: CoordsFloat, P: AsRef<std::path::Path> + std::fmt::Debug> From<P> for CMapBuilder<T> {
213 fn from(value: P) -> Self {
214 let vtk_file =
215 Vtk::import(value).unwrap_or_else(|e| panic!("E: failed to load file: {e:?}"));
216 CMapBuilder {
217 vtk_file: Some(vtk_file),
218 ..Default::default()
219 }
220 }
221}
222
223macro_rules! if_predicate_return_err {
226 ($pr: expr, $er: expr) => {
227 if $pr {
228 return Err($er);
229 }
230 };
231}
232
233macro_rules! build_vertices {
234 ($v: ident) => {{
235 if_predicate_return_err!(
236 !($v.len() % 3).is_zero(),
237 BuilderError::BadVtkData("vertex list contains an incomplete tuple")
238 );
239 $v.chunks_exact(3)
240 .map(|slice| {
241 let &[x, y, _] = slice else { unreachable!() };
243 Vertex2(T::from(x).unwrap(), T::from(y).unwrap())
244 })
245 .collect()
246 }};
247}
248
249#[allow(clippy::too_many_lines)]
250pub fn build_2d_from_vtk<T: CoordsFloat>(
271 value: Vtk,
272 mut _manager: AttrStorageManager, ) -> Result<CMap2<T>, BuilderError> {
274 let mut cmap: CMap2<T> = CMap2::new(0);
275 let mut sew_buffer: BTreeMap<(usize, usize), DartIdType> = BTreeMap::new();
276 match value.data {
277 DataSet::ImageData { .. }
278 | DataSet::StructuredGrid { .. }
279 | DataSet::RectilinearGrid { .. }
280 | DataSet::PolyData { .. }
281 | DataSet::Field { .. } => {
282 return Err(BuilderError::UnsupportedVtkData("dataset not supported"))
283 }
284 DataSet::UnstructuredGrid { pieces, .. } => {
285 let mut tmp = pieces.iter().map(|piece| {
286 let Ok(tmp) = piece.load_piece_data(None) else {
288 return Err(BuilderError::UnsupportedVtkData("not inlined data piece"));
289 };
290
291 let vertices: Vec<Vertex2<T>> = match tmp.points {
295 IOBuffer::F64(v) => build_vertices!(v),
296 IOBuffer::F32(v) => build_vertices!(v),
297 _ => {
298 return Err(BuilderError::UnsupportedVtkData(
299 "unsupported coordinate type",
300 ))
301 }
302 };
303
304 let vtkio::model::Cells { cell_verts, types } = tmp.cells;
305 match cell_verts {
306 VertexNumbers::Legacy {
307 num_cells,
308 vertices: verts,
309 } => {
310 if_predicate_return_err!(
312 num_cells as usize != types.len(),
313 BuilderError::BadVtkData("different # of cell in CELLS and CELL_TYPES")
314 );
315
316 let mut cell_components: Vec<Vec<usize>> = Vec::new();
318 let mut take_next = 0;
319 for vertex_id in &verts {
320 if take_next.is_zero() {
321 take_next = *vertex_id as usize;
323 cell_components.push(Vec::with_capacity(take_next));
324 } else {
325 cell_components
326 .last_mut()
327 .expect("E: unreachable")
328 .push(*vertex_id as usize);
329 take_next -= 1;
330 }
331 }
332 assert_eq!(num_cells as usize, cell_components.len());
333
334 let mut errs =
335 types
336 .iter()
337 .zip(cell_components.iter())
338 .map(|(cell_type, vids)| match cell_type {
339 CellType::Vertex => {
340 if_predicate_return_err!(
341 vids.len() != 1,
342 BuilderError::BadVtkData(
343 "`Vertex` with incorrect # of vertices (!=1)"
344 )
345 );
346 Ok(())
348 }
349 CellType::PolyVertex => Err(BuilderError::UnsupportedVtkData(
350 "`PolyVertex` cell type",
351 )),
352 CellType::Line => {
353 if_predicate_return_err!(
354 vids.len() != 2,
355 BuilderError::BadVtkData(
356 "`Line` with incorrect # of vertices (!=2)"
357 )
358 );
359 Ok(())
361 }
362 CellType::PolyLine => Err(BuilderError::UnsupportedVtkData(
363 "`PolyLine` cell type",
364 )),
365 CellType::Triangle => {
366 if_predicate_return_err!(
368 vids.len() != 3,
369 BuilderError::BadVtkData(
370 "`Triangle` with incorrect # of vertices (!=3)"
371 )
372 );
373 let d0 = cmap.add_free_darts(3);
375 let (d1, d2) = (d0 + 1, d0 + 2);
376 cmap.force_write_vertex(
377 d0 as VertexIdType,
378 vertices[vids[0]],
379 );
380 cmap.force_write_vertex(
381 d1 as VertexIdType,
382 vertices[vids[1]],
383 );
384 cmap.force_write_vertex(
385 d2 as VertexIdType,
386 vertices[vids[2]],
387 );
388 cmap.force_link::<1>(d0, d1).unwrap(); cmap.force_link::<1>(d1, d2).unwrap(); cmap.force_link::<1>(d2, d0).unwrap(); sew_buffer.insert((vids[0], vids[1]), d0);
393 sew_buffer.insert((vids[1], vids[2]), d1);
394 sew_buffer.insert((vids[2], vids[0]), d2);
395 Ok(())
396 }
397 CellType::TriangleStrip => {
398 Err(BuilderError::UnsupportedVtkData(
399 "`TriangleStrip` cell type",
400 ))
401 }
402 CellType::Polygon => {
403 let n_vertices = vids.len();
404 let d0 = cmap.add_free_darts(n_vertices);
405 (0..n_vertices).for_each(|i| {
406 let di = d0 + i as DartIdType;
407 let dip1 =
408 if i == n_vertices - 1 { d0 } else { di + 1 };
409 cmap.force_write_vertex(
410 di as VertexIdType,
411 vertices[vids[i]],
412 );
413 cmap.force_link::<1>(di, dip1).unwrap();
414 sew_buffer
415 .insert((vids[i], vids[(i + 1) % n_vertices]), di);
416 });
417 Ok(())
418 }
419 CellType::Pixel => {
420 Err(BuilderError::UnsupportedVtkData("`Pixel` cell type"))
421 }
422 CellType::Quad => {
423 if_predicate_return_err!(
424 vids.len() != 4,
425 BuilderError::BadVtkData(
426 "`Quad` with incorrect # of vertices (!=4)"
427 )
428 );
429 let d0 = cmap.add_free_darts(4);
431 let (d1, d2, d3) = (d0 + 1, d0 + 2, d0 + 3);
432 cmap.force_write_vertex(
433 d0 as VertexIdType,
434 vertices[vids[0]],
435 );
436 cmap.force_write_vertex(
437 d1 as VertexIdType,
438 vertices[vids[1]],
439 );
440 cmap.force_write_vertex(
441 d2 as VertexIdType,
442 vertices[vids[2]],
443 );
444 cmap.force_write_vertex(
445 d3 as VertexIdType,
446 vertices[vids[3]],
447 );
448 cmap.force_link::<1>(d0, d1).unwrap(); cmap.force_link::<1>(d1, d2).unwrap(); cmap.force_link::<1>(d2, d3).unwrap(); cmap.force_link::<1>(d3, d0).unwrap(); sew_buffer.insert((vids[0], vids[1]), d0);
454 sew_buffer.insert((vids[1], vids[2]), d1);
455 sew_buffer.insert((vids[2], vids[3]), d2);
456 sew_buffer.insert((vids[3], vids[0]), d3);
457 Ok(())
458 }
459 _ => Err(BuilderError::UnsupportedVtkData(
460 "CellType not supported in 2-maps",
461 )),
462 });
463 if let Some(is_err) = errs.find(Result::is_err) {
464 return Err(is_err.unwrap_err()); }
466 }
467 VertexNumbers::XML { .. } => {
468 return Err(BuilderError::UnsupportedVtkData("XML format"));
469 }
470 }
471 Ok(())
472 });
473 if let Some(is_err) = tmp.find(Result::is_err) {
475 return Err(is_err.unwrap_err()); }
477 }
478 }
479 while let Some(((id0, id1), dart_id0)) = sew_buffer.pop_first() {
480 if let Some(dart_id1) = sew_buffer.remove(&(id1, id0)) {
481 cmap.force_sew::<2>(dart_id0, dart_id1).unwrap();
482 }
483 }
484 Ok(cmap)
485}