honeycomb_core/cmap/builder/
structure.rs

1use std::fs::File;
2use std::io::Read;
3
4use thiserror::Error;
5use vtkio::Vtk;
6
7use crate::attributes::{AttrStorageManager, AttributeBind};
8use crate::cmap::{CMap2, GridDescriptor};
9use crate::geometry::CoordsFloat;
10
11use super::io::CMapFile;
12
13/// # Builder-level error enum
14///
15/// This enum is used to describe all non-panic errors that can occur when using the builder
16/// structure.
17#[derive(Error, Debug, PartialEq, Eq)]
18pub enum BuilderError {
19    // grid-related variants
20    /// One or multiple of the specified grid characteristics are invalid.
21    #[error("invalid grid parameters - {0}")]
22    InvalidGridParameters(&'static str),
23    /// The builder is missing one or multiple parameters to generate the grid.
24    #[error("insufficient parameters - please specifiy at least 2")]
25    MissingGridParameters,
26
27    // custom format variants
28    /// A value could not be parsed.
29    #[error("error parsing a value in one of the cmap file section - {0}")]
30    BadValue(&'static str),
31    /// The meta section of the file is incorrect.
32    #[error("error parsing the cmap file meta section - {0}")]
33    BadMetaData(&'static str),
34    /// The file contains a duplicated section.
35    #[error("duplicated section in cmap file - {0}")]
36    DuplicatedSection(String),
37    /// The file contains contradicting data.
38    #[error("inconsistent data - {0}")]
39    InconsistentData(&'static str),
40    /// A required section is missing from the file.
41    #[error("required section missing in cmap file - {0}")]
42    MissingSection(&'static str),
43    /// The file contains an unrecognized section header.
44    #[error("unknown header in cmap file - {0}")]
45    UnknownHeader(String),
46
47    // vtk-related variants
48    /// Specified VTK file contains inconsistent data.
49    #[error("invalid/corrupted data in the vtk file - {0}")]
50    BadVtkData(&'static str),
51    /// Specified VTK file contains unsupported data.
52    #[error("unsupported data in the vtk file - {0}")]
53    UnsupportedVtkData(&'static str),
54}
55
56/// # Combinatorial map builder structure
57///
58/// ## Example
59///
60/// ```rust
61/// # use honeycomb_core::cmap::BuilderError;
62/// # fn main() -> Result<(), BuilderError> {
63/// use honeycomb_core::cmap::{CMap2, CMapBuilder};
64///
65/// let builder = CMapBuilder::default().n_darts(10);
66/// let map: CMap2<f64> = builder.build()?;
67///
68/// assert_eq!(map.n_darts(), 11); // 10 + null dart = 11
69///
70/// # Ok(())
71/// # }
72/// ```
73#[derive(Default)]
74pub struct CMapBuilder<T>
75where
76    T: CoordsFloat,
77{
78    pub(super) cmap_file: Option<CMapFile>,
79    pub(super) vtk_file: Option<Vtk>,
80    pub(super) grid_descriptor: Option<GridDescriptor<T>>,
81    pub(super) attributes: AttrStorageManager,
82    pub(super) n_darts: usize,
83    pub(super) coordstype: std::marker::PhantomData<T>,
84}
85
86// --- `From` impls & predefinite values
87
88impl<T: CoordsFloat> From<GridDescriptor<T>> for CMapBuilder<T> {
89    fn from(value: GridDescriptor<T>) -> Self {
90        CMapBuilder {
91            grid_descriptor: Some(value),
92            ..Default::default()
93        }
94    }
95}
96
97/// # Regular methods
98impl<T: CoordsFloat> CMapBuilder<T> {
99    /// Set the number of dart that the created map will contain.
100    #[must_use = "unused builder object"]
101    pub fn n_darts(mut self, n_darts: usize) -> Self {
102        self.n_darts = n_darts;
103        self
104    }
105
106    /// Set the [`GridDescriptor`] that will be used when building the map.
107    #[must_use = "unused builder object"]
108    pub fn grid_descriptor(mut self, grid_descriptor: GridDescriptor<T>) -> Self {
109        self.grid_descriptor = Some(grid_descriptor);
110        self
111    }
112
113    /// Set the `cmap` file that will be used when building the map.
114    ///
115    /// # Panics
116    ///
117    /// This function may panic if the file cannot be loaded, or basic section parsing fails.
118    #[must_use = "unused builder object"]
119    pub fn cmap_file(mut self, file_path: impl AsRef<std::path::Path> + std::fmt::Debug) -> Self {
120        let mut f = File::open(file_path).expect("E: could not open specified file");
121        let mut buf = String::new();
122        f.read_to_string(&mut buf)
123            .expect("E: could not read content from file");
124        let cmap_file = CMapFile::try_from(buf).unwrap();
125        self.cmap_file = Some(cmap_file);
126        self
127    }
128
129    /// Set the VTK file that will be used when building the map.
130    ///
131    /// # Panics
132    ///
133    /// This function may panic if the file cannot be loaded.
134    #[must_use = "unused builder object"]
135    pub fn vtk_file(mut self, file_path: impl AsRef<std::path::Path> + std::fmt::Debug) -> Self {
136        let vtk_file =
137            Vtk::import(file_path).unwrap_or_else(|e| panic!("E: failed to load file: {e:?}"));
138        self.vtk_file = Some(vtk_file);
139        self
140    }
141
142    /// Add the attribute `A` to the attributes the created map will contain.
143    ///
144    /// # Usage
145    ///
146    /// Each attribute must be uniquely typed, i.e. a single type or struct cannot be added twice
147    /// to the builder / map. This includes type aliases as these are not distinct from the
148    /// compiler's perspective.
149    ///
150    /// If you have multiple attributes that are represented using the same data type, you may want
151    /// to look into the **Newtype** pattern
152    /// [here](https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html)
153    /// and [here](https://doc.rust-lang.org/rust-by-example/generics/new_types.html)
154    #[must_use = "unused builder object"]
155    pub fn add_attribute<A: AttributeBind + 'static>(mut self) -> Self {
156        self.attributes.add_storage::<A>(self.n_darts);
157        self
158    }
159
160    #[allow(clippy::missing_errors_doc)]
161    /// Consumes the builder and produce a [`CMap2`] object.
162    ///
163    /// # Return / Errors
164    ///
165    /// This method return a `Result` taking the following values:
166    /// - `Ok(map: CMap2)` if generation was successful,
167    /// - `Err(BuilderError)` otherwise. See [`BuilderError`] for possible failures.
168    ///
169    /// # Panics
170    ///
171    /// This method may panic if type casting goes wrong during parameters parsing.
172    pub fn build(self) -> Result<CMap2<T>, BuilderError> {
173        if let Some(cfile) = self.cmap_file {
174            // build from our custom format
175            return super::io::build_2d_from_cmap_file(cfile, self.attributes);
176        }
177        if let Some(vfile) = self.vtk_file {
178            // build from vtk
179            return super::io::build_2d_from_vtk(vfile, self.attributes);
180        }
181        if let Some(gridb) = self.grid_descriptor {
182            // build from grid descriptor
183            let split = gridb.split_quads;
184            return gridb.parse_2d().map(|(origin, ns, lens)| {
185                if split {
186                    super::grid::build_2d_splitgrid(origin, ns, lens, self.attributes)
187                } else {
188                    super::grid::build_2d_grid(origin, ns, lens, self.attributes)
189                }
190            });
191        }
192        Ok(CMap2::new_with_undefined_attributes(
193            self.n_darts,
194            self.attributes,
195        ))
196    }
197}
198
199/// # Pre-definite structures
200impl<T: CoordsFloat> CMapBuilder<T> {
201    /// Create a [`CMapBuilder`] with a predefinite [`GridDescriptor`] value.
202    ///
203    /// # Arguments
204    ///
205    /// - `n_square: usize` -- Number of cells along each axis.
206    ///
207    /// # Return
208    ///
209    /// This function return a builder structure with pre-definite parameters set to generate
210    /// a specific map.
211    ///
212    /// The map generated by this pre-definite value corresponds to an orthogonal mesh, with an
213    /// equal number of cells along each axis:
214    ///
215    /// ![`CMAP2_GRID`](https://lihpc-computational-geometry.github.io/honeycomb/user-guide/images/bg_grid.svg)
216    #[must_use = "unused builder object"]
217    pub fn unit_grid(n_square: usize) -> Self {
218        GridDescriptor::default()
219            .n_cells([n_square; 3])
220            .len_per_cell([T::one(); 3])
221            .into()
222    }
223
224    /// Create a [`CMapBuilder`] with a predefinite [`GridDescriptor`] value.
225    ///
226    /// # Arguments
227    ///
228    /// - `n_square: usize` -- Number of cells along each axis.
229    ///
230    /// # Return
231    ///
232    /// This function return a builder structure with pre-definite parameters set to generate
233    /// a specific map.
234    ///
235    /// The map generated by this pre-definite value corresponds to an orthogonal mesh, with an
236    /// equal number of cells along each axis. Each cell will be split across their diagonal (top
237    /// left to bottom right) to form triangles:
238    ///
239    /// ![`CMAP2_GRID`](https://lihpc-computational-geometry.github.io/honeycomb/user-guide/images/bg_grid_tri.svg)
240    #[must_use = "unused builder object"]
241    pub fn unit_triangles(n_square: usize) -> Self {
242        GridDescriptor::default()
243            .n_cells([n_square; 3])
244            .len_per_cell([T::one(); 3])
245            .split_quads(true)
246            .into()
247    }
248}