Skip to main content

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, CMap3};
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    // custom format variants
20    /// A value could not be parsed.
21    #[error("error parsing a value in one of the cmap file section - {0}")]
22    BadValue(&'static str),
23    /// The meta section of the file is incorrect.
24    #[error("error parsing the cmap file meta section - {0}")]
25    BadMetaData(&'static str),
26    /// The file contains a duplicated section.
27    #[error("duplicated section in cmap file - {0}")]
28    DuplicatedSection(String),
29    /// The file contains contradicting data.
30    #[error("inconsistent data - {0}")]
31    InconsistentData(&'static str),
32    /// A required section is missing from the file.
33    #[error("required section missing in cmap file - {0}")]
34    MissingSection(&'static str),
35    /// The file contains an unrecognized section header.
36    #[error("unknown header in cmap file - {0}")]
37    UnknownHeader(String),
38
39    // INP-related variants
40    /// Specified Abaqus INP file contains inconsistent or malformed mesh data.
41    #[error("invalid/corrupted data in the inp file - {0}")]
42    BadInpData(&'static str),
43    /// Specified Abaqus INP file contains unsupported mesh data.
44    #[error("unsupported data in the inp file - {0}")]
45    UnsupportedInpData(&'static str),
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, CMap3, CMapBuilder};
64///
65/// let builder_2d = CMapBuilder::<2>::from_n_darts(10);
66/// let map_2d: CMap2<f64> = builder_2d.build()?;
67/// assert_eq!(map_2d.n_darts(), 11); // 10 + null dart = 11
68///
69/// let builder_3d = CMapBuilder::<3>::from_n_darts(10);
70/// let map_3d: CMap3<f64> = builder_3d.build()?;
71/// assert_eq!(map_3d.n_darts(), 11); // 10 + null dart = 11
72/// # Ok(())
73/// # }
74/// ```
75pub struct CMapBuilder<const D: usize> {
76    builder_kind: BuilderType,
77    attributes: AttrStorageManager,
78}
79
80enum BuilderType {
81    CMap(CMapFile),
82    FreeDarts(usize),
83    Inp(String),
84    Vtk(Vtk),
85}
86
87#[doc(hidden)]
88pub trait Builder<T: CoordsFloat> {
89    type MapType;
90    fn build(self) -> Result<Self::MapType, BuilderError>;
91}
92
93impl<T: CoordsFloat> Builder<T> for CMapBuilder<2> {
94    type MapType = CMap2<T>;
95
96    fn build(self) -> Result<Self::MapType, BuilderError> {
97        match self.builder_kind {
98            BuilderType::CMap(cfile) => super::io::build_2d_from_cmap_file(cfile, self.attributes),
99            BuilderType::FreeDarts(n_darts) => Ok(CMap2::new_with_undefined_attributes(
100                n_darts,
101                self.attributes,
102            )),
103            BuilderType::Inp(_) => unreachable!("INP input is only available for 3-maps"),
104            BuilderType::Vtk(vfile) => super::io::build_2d_from_vtk(vfile, self.attributes),
105        }
106    }
107}
108
109impl<T: CoordsFloat> Builder<T> for CMapBuilder<3> {
110    type MapType = CMap3<T>;
111
112    fn build(self) -> Result<Self::MapType, BuilderError> {
113        match self.builder_kind {
114            BuilderType::CMap(cfile) => super::io::build_3d_from_cmap_file(cfile, self.attributes),
115            BuilderType::FreeDarts(n_darts) => Ok(CMap3::new_with_undefined_attributes(
116                n_darts,
117                self.attributes,
118            )),
119            BuilderType::Inp(content) => super::io::build_3d_from_inp(&content, self.attributes),
120            BuilderType::Vtk(_vfile) => unimplemented!(),
121        }
122    }
123}
124
125impl CMapBuilder<3> {
126    /// Create a builder structure from an Abaqus INP file containing C3D8 hexahedra.
127    ///
128    /// Node coordinates and hexahedral connectivity are imported. Other Abaqus data, such as
129    /// sets, materials, sections, surfaces, and analysis steps, is ignored.
130    ///
131    /// # Panics
132    ///
133    /// This function may panic if the file cannot be opened or read. Invalid INP content is
134    /// reported by [`Self::build`].
135    #[must_use = "unused builder object"]
136    pub fn from_inp_file(file_path: impl AsRef<std::path::Path> + std::fmt::Debug) -> Self {
137        let mut f = File::open(file_path).expect("E: could not open specified file");
138        let mut content = String::new();
139        f.read_to_string(&mut content)
140            .expect("E: could not read content from file");
141
142        Self {
143            builder_kind: BuilderType::Inp(content),
144            attributes: AttrStorageManager::default(),
145        }
146    }
147}
148/// # Regular methods
149impl<const D: usize> CMapBuilder<D> {
150    /// Create a builder structure for a map with a set number of darts and the attribute set of
151    /// another builder.
152    #[must_use = "unused builder object"]
153    pub fn from_n_darts_and_attributes(n_darts: usize, other: Self) -> Self {
154        Self {
155            builder_kind: BuilderType::FreeDarts(n_darts),
156            attributes: other.attributes,
157        }
158    }
159
160    /// Create a builder structure for a map with a set number of darts.
161    #[must_use = "unused builder object"]
162    pub fn from_n_darts(n_darts: usize) -> Self {
163        Self {
164            builder_kind: BuilderType::FreeDarts(n_darts),
165            attributes: AttrStorageManager::default(),
166        }
167    }
168
169    /// Create a builder structure from a `cmap` file.
170    ///
171    /// # Panics
172    ///
173    /// This function may panic if the file cannot be loaded, or basic section parsing fails.
174    #[must_use = "unused builder object"]
175    pub fn from_cmap_file(file_path: impl AsRef<std::path::Path> + std::fmt::Debug) -> Self {
176        let mut f = File::open(file_path).expect("E: could not open specified file");
177        let mut buf = String::new();
178        f.read_to_string(&mut buf)
179            .expect("E: could not read content from file");
180        let cmap_file = CMapFile::try_from(buf).unwrap();
181
182        Self {
183            builder_kind: BuilderType::CMap(cmap_file),
184            attributes: AttrStorageManager::default(),
185        }
186    }
187
188    /// Create a builder structure from a VTK file.
189    ///
190    /// # Panics
191    ///
192    /// This function may panic if the file cannot be loaded.
193    #[must_use = "unused builder object"]
194    pub fn from_vtk_file(file_path: impl AsRef<std::path::Path> + std::fmt::Debug) -> Self {
195        let vtk_file =
196            Vtk::import(file_path).unwrap_or_else(|e| panic!("E: failed to load file: {e:?}"));
197
198        Self {
199            builder_kind: BuilderType::Vtk(vtk_file),
200            attributes: AttrStorageManager::default(),
201        }
202    }
203
204    /// Add the attribute `A` to the attributes the created map will contain.
205    ///
206    /// # Usage
207    ///
208    /// Each attribute must be uniquely typed, i.e. a single type or struct cannot be added twice
209    /// to the builder / map. This includes type aliases as these are not distinct from the
210    /// compiler's perspective.
211    ///
212    /// If you have multiple attributes that are represented using the same data type, you may want
213    /// to look into the **Newtype** pattern
214    /// [here](https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html)
215    /// and [here](https://doc.rust-lang.org/rust-by-example/generics/new_types.html)
216    #[must_use = "unused builder object"]
217    pub fn add_attribute<A: AttributeBind + 'static>(mut self) -> Self {
218        self.attributes.add_storage::<A>(1);
219        self
220    }
221
222    #[allow(clippy::missing_errors_doc)]
223    /// Consumes the builder and produce a combinatorial map object.
224    ///
225    /// # Return / Errors
226    ///
227    /// This method return a `Result` taking the following values:
228    /// - `Ok(map: _)` if generation was successful,
229    /// - `Err(BuilderError)` otherwise. See [`BuilderError`] for possible failures.
230    ///
231    /// Depending on the dimension `D` associated with this structure, the map will either be a
232    /// `CMap2` or `CMap3`. If `D` isn't 2 or 3, this method will not be available as it uses a
233    /// trait not implemented for other values of `D`. This is necessary to handle the multiple
234    /// return types as Rust is slightly lacking in terms of comptime capabilities.
235    ///
236    /// # Panics
237    ///
238    /// This method may panic if type casting goes wrong during parameters parsing.
239    #[allow(private_interfaces, private_bounds)]
240    pub fn build<T: CoordsFloat>(self) -> Result<<Self as Builder<T>>::MapType, BuilderError>
241    where
242        Self: Builder<T>,
243    {
244        Builder::build(self)
245    }
246}