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 /// 
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 /// 
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}